Merge remote-tracking branch 'origin/main' into video_file_preview
12
.github/workflows/auth-lint.yml
vendored
@@ -30,6 +30,18 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Verify all icons are less than 20KB
|
||||
run: |
|
||||
find assets/custom-icons -type f -name "*.svg" | while read -r file; do
|
||||
if [[ "$file" == "assets/custom-icons/icons/bbs_nga.svg" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "$(stat --printf="%s" "$file")" -gt 20480 ]]; then
|
||||
echo "File size is greater than 20KB: $file ($file_size bytes)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Verify custom icon JSON
|
||||
run: cat assets/custom-icons/_data/custom-icons.json | jq empty
|
||||
|
||||
@@ -30,10 +30,10 @@ Learn more at [ente.io](https://ente.io).
|
||||
|
||||

|
||||
|
||||
Our flagship product. 3x data replication. On device machine learning. Cross
|
||||
platform. Private sharing. Collaborative albums. Family plans. Easy import,
|
||||
easier export. Background uploads. The list goes on. And of course, all of this,
|
||||
while being fully end-to-end encrypted.
|
||||
Our flagship product. 3x data replication. Face detection. Semantic search.
|
||||
Private sharing. Collaborative albums. Family plans. Easy import, easier export.
|
||||
Background uploads. The list goes on. And of course, all of this, while being
|
||||
fully end-to-end encrypted across platforms.
|
||||
|
||||
Ente Photos is a paid service, but we offer 5GB of free storage.
|
||||
You can also clone this repository and choose to self-host.
|
||||
|
||||
@@ -85,6 +85,17 @@
|
||||
"币安"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Binance TR",
|
||||
"slug": "binance_tr"
|
||||
},
|
||||
{
|
||||
"title": "BinanceUS",
|
||||
"slug": "binance_us",
|
||||
"altNames": [
|
||||
"Binance US"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Bitfinex"
|
||||
},
|
||||
@@ -258,6 +269,10 @@
|
||||
{
|
||||
"title": "Dropbox"
|
||||
},
|
||||
{
|
||||
"title": "DreamHost Panel",
|
||||
"slug": "dreamhost_panel"
|
||||
},
|
||||
{
|
||||
"title": "dus.net",
|
||||
"slug": "dusnet"
|
||||
@@ -433,6 +448,9 @@
|
||||
{
|
||||
"title": "Kite"
|
||||
},
|
||||
{
|
||||
"title": "Kotas"
|
||||
},
|
||||
{
|
||||
"title": "KnownHost",
|
||||
"altNames": [
|
||||
@@ -515,7 +533,9 @@
|
||||
},
|
||||
{
|
||||
"title": "matlab",
|
||||
"altNames": ["mathworks"]
|
||||
"altNames": [
|
||||
"mathworks"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mercado Livre",
|
||||
@@ -527,7 +547,7 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Microsoft"
|
||||
"title": "microsoft"
|
||||
},
|
||||
{
|
||||
"title": "Microsoft 365",
|
||||
@@ -602,6 +622,9 @@
|
||||
"title": "ngrok",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Nelnet"
|
||||
},
|
||||
{
|
||||
"title": "nintendo",
|
||||
"altNames": [
|
||||
@@ -611,6 +634,14 @@
|
||||
{
|
||||
"title": "Njalla"
|
||||
},
|
||||
{
|
||||
"title": "nordvpn",
|
||||
"slug": "nordaccount",
|
||||
"hex": "#4687FF",
|
||||
"altNames": [
|
||||
"Nord Account"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Notesnook"
|
||||
},
|
||||
@@ -715,7 +746,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "randstad"
|
||||
"title": "randstad",
|
||||
"hex": "#2175D9"
|
||||
},
|
||||
{
|
||||
"title": "Real-Debrid",
|
||||
@@ -947,6 +979,10 @@
|
||||
{
|
||||
"title": "Upstox"
|
||||
},
|
||||
{
|
||||
"title": "US Mobile",
|
||||
"slug": "us_mobile"
|
||||
},
|
||||
{
|
||||
"title": "Vikunja"
|
||||
},
|
||||
@@ -957,7 +993,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "WARGAMING.NET"
|
||||
"title": "WARGAMING.NET",
|
||||
"slug": "wargamingnet"
|
||||
},
|
||||
{
|
||||
"title": "Wealthfront"
|
||||
@@ -1009,4 +1046,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.3 MiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.1 MiB After Width: | Height: | Size: 785 B |
14
auth/assets/custom-icons/icons/dreamhost_panel.svg
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<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"
|
||||
width="160.5px" height="160.5px" viewBox="0 0 160.5 160.5" enable-background="new 0 0 160.5 160.5" xml:space="preserve">
|
||||
<g>
|
||||
<path d="M145.794,113.854c-11.111,5.932-23.811,9.227-37.417,9.008c-40.366-0.879-72.114-32.293-71.434-70.515
|
||||
c0.227-12.961,4.309-25.042,11.112-35.148C26.512,28.841,11.771,50.589,11.318,75.851c-0.907,38.002,31.068,69.636,71.208,70.515
|
||||
C109.284,146.805,132.642,133.844,145.794,113.854z"/>
|
||||
<path fill="#0073EC" d="M86.833,14.123c-10.885,0-21.09,2.636-30.161,7.469c-5.669,9.007-8.844,19.331-9.297,29.875
|
||||
c-0.68,33.171,27.212,60.63,61.909,61.289c10.432,0,22.224-2.417,31.294-7.249c5.443-9.007,8.617-19.551,8.617-30.754
|
||||
C149.648,41.362,121.53,14.123,86.833,14.123z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 19 KiB |
5
auth/assets/custom-icons/icons/kotas.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<g style="mix-blend-mode:difference">
|
||||
<path d="M105.08 0.61C90.5 2.16 71.47 8.21 57.3 15.82C28.49 31.36 10.26 55.31 3.69 86.29C0.94 99.26 1.00 94.06 1.00 249.17C1.00 386.74 1.06 395.6 2.03 401.78C5.40 423.61 13.35 442.47 25.46 457.5C30.32 463.56 40.49 473.27 46.27 477.39C65.24 490.93 88.90 498.99 112.16 499.91C139.48 500.99 165.60 491.62 188.29 472.53C194.29 467.50 222.01 440.24 233.90 427.72C241.45 419.78 244.13 418.06 249.33 418.12C255.22 418.12 258.88 420.86 269.62 433.15C278.20 442.93 306.54 470.07 314.09 475.67C324.49 483.45 332.66 488.02 343.92 492.36C388.79 509.62 438.68 497.05 470.23 460.47C504.47 420.86 507.90 364.91 478.80 320.90C472.18 310.79 466.52 304.56 441.48 279.58C424.57 262.72 417.59 255.35 416.79 253.58C414.05 247.57 414.96 246.20 435.43 226.14C456.63 205.34 473.20 187.91 477.55 181.85C488.18 166.99 494.35 152.47 497.21 135.67C498.64 127.04 498.64 108.52 497.15 99.72C491.38 64.23 475.20 38.05 448.00 19.93C428.68 7.07 407.19 0.61 383.82 0.56C356.27 0.56 332.66 8.84 311.34 26.10C305.57 30.79 276.77 58.97 263.56 72.91C256.36 80.51 254.02 82.17 249.85 82.69C248.19 82.91 246.88 82.63 244.48 81.49C241.96 80.23 239.39 77.83 231.10 68.97C212.18 48.68 193.15 30.56 182.75 22.85C158.74 5.19 132.74 -2.30 105.08 0.61ZM397.19 49.37C405.71 51.19 413.65 54.51 420.79 59.14C426.85 63.03 437.43 73.88 441.26 80.17C453.43 99.83 454.91 122.69 445.31 142.87C440.17 153.67 439.20 154.81 397.59 196.88C356.95 237.97 315.80 279.01 289.86 304.33C277.74 316.22 274.37 319.82 273.17 322.27C271.28 326.16 271.17 329.36 272.94 332.50C274.71 335.76 292.66 353.94 295.51 355.42C298.66 357.02 302.49 356.97 305.80 355.31C307.46 354.51 310.43 351.82 314.15 347.76C317.29 344.28 331.46 329.88 345.64 315.70C363.07 298.27 372.10 289.58 373.64 288.90C376.44 287.64 380.04 287.64 382.44 288.90C385.19 290.33 433.02 338.73 437.37 344.51C448.28 359.02 453.31 377.08 451.03 393.54C446.23 428.35 411.42 457.04 378.67 453.33C365.24 451.78 354.38 447.16 342.55 437.90C336.66 433.32 161.55 258.78 159.03 255.00C156.74 251.58 156.74 248.60 158.97 245.12C159.83 243.75 175.20 228.09 193.09 210.31C213.10 190.48 226.01 177.22 226.64 175.90C228.81 171.33 227.33 167.56 221.21 162.19C218.98 160.19 214.58 156.24 211.50 153.44C198.64 141.67 197.72 141.15 194.01 143.15C191.61 144.41 104.79 231.80 101.42 236.32C95.65 244.09 94.56 250.49 97.82 257.52C98.79 259.63 101.08 263.18 102.91 265.41C104.68 267.64 129.25 292.55 157.49 320.79C188.58 351.94 209.21 373.03 209.90 374.34C211.38 377.26 211.38 381.20 209.84 384.23C209.15 385.60 206.01 389.09 202.52 392.29C199.21 395.43 187.61 406.92 176.75 417.84C155.49 439.27 151.20 442.87 141.60 447.56C127.14 454.59 111.88 455.44 95.88 450.13C73.59 442.64 56.78 427.09 50.95 408.63C47.69 398.29 47.92 409.66 47.75 253.46C47.58 126.12 47.69 109.32 48.44 103.78C50.61 88.86 56.61 77.08 67.07 67.14C76.56 58.11 88.96 51.65 101.13 49.37C108.68 47.99 122.51 48.45 130.00 50.45C141.08 53.37 150.23 57.94 158.92 64.91C161.37 66.85 198.18 103.43 240.87 146.18C301.86 207.39 319.00 224.26 321.29 225.40C325.35 227.46 329.23 227.40 333.06 225.23C336.09 223.51 341.24 218.37 347.98 210.31C355.12 201.79 356.21 198.19 353.07 192.82C351.07 189.39 322.09 159.67 297.11 135.44C285.74 124.41 284.54 121.27 289.11 114.52C290.20 112.86 292.71 110.18 294.66 108.52C296.60 106.86 307.57 96.06 319.06 84.57C337.58 65.94 340.61 63.14 346.09 59.54C354.67 53.94 364.50 50.11 374.21 48.57C379.01 47.82 392.27 48.28 397.19 49.37Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
33
auth/assets/custom-icons/icons/nelnet.svg
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<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"
|
||||
width="167.5px" height="167.5px" viewBox="0 0 167.5 167.5" enable-background="new 0 0 167.5 167.5" xml:space="preserve">
|
||||
<g>
|
||||
<defs>
|
||||
<rect id="SVGID_1_" x="3.75" y="3.75" width="160" height="160"/>
|
||||
</defs>
|
||||
<clipPath id="SVGID_2_">
|
||||
<use xlink:href="#SVGID_1_" overflow="visible"/>
|
||||
</clipPath>
|
||||
<g id="icon-circle-n" clip-path="url(#SVGID_2_)">
|
||||
<rect x="3.75" y="3.75" fill="#FFFFFF" width="160" height="160"/>
|
||||
<g id="Group_1" transform="translate(-283.001 134)">
|
||||
<path id="Path_926" fill="#A2AAAD" d="M371.633-89.707c-8.596,0-14.667,3.859-19.81,8.422l-0.283-5.307
|
||||
c-0.027-0.655-0.562-1.173-1.217-1.178h-16.4c-0.68,0.004-1.229,0.56-1.224,1.24c0,0.001,0,0.003,0,0.004v74.489
|
||||
c-0.003,0.678,0.542,1.23,1.221,1.236h18.258c0.68-0.004,1.228-0.557,1.225-1.236v-51.36c6.662-7.678,9.619-8.926,14.025-8.926
|
||||
c6.132,0,12.679,4.229,12.679,16.1v44.186c-0.004,0.678,0.543,1.231,1.221,1.236c0,0,0,0,0.001,0h18.259
|
||||
c0.678-0.006,1.225-0.559,1.221-1.236v-47.985C400.81-79.507,386.134-89.707,371.633-89.707"/>
|
||||
|
||||
<linearGradient id="Path_929_1_" gradientUnits="userSpaceOnUse" x1="59.4341" y1="340.5049" x2="59.4341" y2="339.5049" gradientTransform="matrix(160 0 0 -160 -9142.6602 54350.5)">
|
||||
<stop offset="0" style="stop-color:#AFD135"/>
|
||||
<stop offset="1" style="stop-color:#70BA44"/>
|
||||
</linearGradient>
|
||||
<path id="Path_929" fill="url(#Path_929_1_)" d="M366.751-130.25c-44.162,0.05-79.95,35.838-80,80l0,0
|
||||
c0.051,44.162,35.838,79.949,80,80l0,0c44.162-0.051,79.949-35.838,80-80l0,0C446.701-94.412,410.913-130.2,366.751-130.25z
|
||||
M303.223-50.25c0.04-35.069,28.459-63.487,63.528-63.527l0,0c35.065,0.043,63.479,28.459,63.521,63.525l0,0
|
||||
c-0.042,35.065-28.456,63.481-63.521,63.525l0,0c-35.066-0.043-63.483-28.459-63.524-63.525"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
1
auth/assets/custom-icons/icons/nordaccount.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>NordVPN</title><path d="M2.2838 21.5414A11.9866 11.9866 0 010 14.4832C0 7.8418 5.3727 2.4586 12 2.4586c6.6279 0 12 5.3832 12 12.0246a11.9853 11.9853 0 01-2.2838 7.0582l-5.7636-9.3783-.5565.9419.5645 2.6186L12 8.9338l-2.45 4.1447.5707 2.6451-2.0764-3.5555-5.7605 9.3733z"/></svg>
|
||||
|
After Width: | Height: | Size: 356 B |
@@ -1,19 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.0" width="990.35431" height="207.28346" id="svg2">
|
||||
<defs id="defs4"/>
|
||||
<g transform="translate(116.92129,-338.61963)" id="layer1">
|
||||
<g transform="translate(35.40372,35.392161)" id="g31225">
|
||||
<path d="M -28.795615,415.09001 C -28.795615,415.09001 -59.115451,384.77025 -59.125465,384.77025 C -60.927824,382.91774 -63.370971,381.90646 -66.024482,381.90646 L -116.92129,381.90646 L -116.92129,401.27187 L -61.19811,401.27187 C -56.952604,401.27187 -52.957357,402.92402 -49.953371,405.92801 C -46.949465,408.93191 -45.297309,412.91715 -45.297309,417.17275 L -45.297309,472.88583 L -25.941913,472.88583 L -25.941913,421.98902 C -25.941913,419.34561 -26.953202,416.89237 -28.795615,415.09001" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path38"/>
|
||||
<path d="M 25.195234,381.90646 C 22.551768,381.90646 20.09856,382.91774 18.296201,384.77025 L -12.023635,415.09001 C -13.866049,416.89237 -14.877338,419.34561 -14.877338,421.98902 L -14.877338,472.88583 L 4.4780586,472.88583 L 4.4780586,417.17275 C 4.4780586,408.40119 11.607388,401.27187 20.378939,401.27187 L 76.09204,401.27187 L 76.09204,381.90646 L 25.195234,381.90646" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path42"/>
|
||||
<path d="M 209.20675,379.69354 C 199.83445,379.69354 190.17176,386.31226 185.7059,395.80471 L 185.5557,396.11513 L 184.29404,396.11513 L 184.29404,381.90646 L 169.60477,381.90646 L 169.60477,472.88583 L 185.36545,472.88583 L 185.36545,431.53162 C 185.36545,408.36114 195.43867,393.39152 211.00911,393.39152 C 214.37352,393.39152 217.30737,393.91222 219.85071,394.8434 L 219.85071,380.85511 C 216.47628,380.17419 213.99302,379.69354 209.20675,379.69354" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path46"/>
|
||||
<path d="M 694.79406,388.47511 C 688.87629,382.54732 679.95458,379.67351 667.50825,379.67351 C 657.2648,379.67351 647.27168,382.50727 637.79925,388.04452 L 638.61032,401.2218 C 645.55944,395.64449 655.47244,392.18997 664.8047,392.18997 C 672.78517,392.18997 678.48265,394.01236 682.23757,397.76728 C 686.21279,401.73249 688.06522,407.81047 688.06522,416.88235 L 688.06522,417.47315 L 687.48446,417.4331 C 680.77566,417.07261 676.03944,417.07261 669.49085,417.08262 C 658.30617,417.07261 643.26643,420.16664 634.56501,428.86809 C 629.74868,433.68437 627.30548,439.74233 627.30548,446.85171 C 627.30548,455.20262 629.70863,461.83135 634.44485,466.57761 C 640.04219,472.16493 649.04401,475.11877 660.47902,475.11877 C 676.93061,475.11877 685.55192,465.50616 688.49579,461.37073 L 688.656,461.14041 L 689.85757,461.14041 L 689.85757,472.8658 L 703.82591,472.8658 L 703.82591,417.17275 C 703.82591,403.93539 700.88205,394.55301 694.79406,388.47511 z M 688.06522,428.93819 L 688.06522,437.61954 C 688.06522,444.83907 685.75219,450.99717 681.39648,455.43293 C 676.7704,460.12912 670.04157,462.60239 661.92091,462.61241 C 656.97442,462.61241 651.83768,460.53968 648.5033,457.21526 C 645.72966,454.43165 644.24772,450.88702 644.24772,446.95185 C 644.24772,430.37005 666.9375,428.5777 676.70031,428.5777 C 678.51268,428.5777 680.35511,428.66782 682.12743,428.75795 C 683.89976,428.84807 685.72215,428.93819 687.5145,428.93819 L 688.06522,428.93819" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path50"/>
|
||||
<path d="M 285.2165,388.47511 C 279.28873,382.54732 270.36701,379.67351 257.92068,379.67351 C 247.67724,379.67351 237.68412,382.50727 228.21168,388.04452 L 229.02275,401.2218 C 235.97188,395.64449 245.88488,392.18997 255.21713,392.18997 C 263.1976,392.18997 268.9051,394.01236 272.65001,397.76728 C 276.62523,401.73249 278.47766,407.81047 278.47766,416.88235 L 278.47766,417.47315 L 277.8969,417.4331 C 271.1981,417.07261 266.46189,417.07261 259.90328,417.08262 C 248.72861,417.07261 233.67886,420.16664 224.98746,428.86809 C 220.16112,433.68437 217.71791,439.74233 217.71791,446.85171 C 217.71791,455.20262 220.12107,461.83135 224.86729,466.57761 C 230.45463,472.16493 239.45645,475.11877 250.89145,475.11877 C 267.34305,475.11877 275.96436,465.50616 278.90823,461.37073 L 279.07845,461.14041 L 280.27001,461.14041 L 280.27001,472.8658 L 294.24836,472.8658 L 294.24836,417.17275 C 294.24836,403.93539 291.28447,394.55301 285.2165,388.47511 z M 278.47766,428.93819 L 278.47766,437.61954 C 278.47766,444.83907 276.16463,450.99717 271.80891,455.43293 C 267.19285,460.12912 260.454,462.60239 252.33335,462.61241 C 247.38685,462.61241 242.25011,460.53968 238.92575,457.21526 C 236.1421,454.43165 234.67016,450.88702 234.67016,446.95185 C 234.67016,430.37005 257.35994,428.5777 267.11274,428.5777 C 268.92512,428.5777 270.76754,428.66782 272.54988,428.75795 C 274.31219,428.84807 276.13459,428.93819 277.92694,428.93819 L 278.47766,428.93819" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path54"/>
|
||||
<path d="M 379.59037,388.13464 C 373.973,382.51728 365.87236,379.67351 355.50876,379.67351 C 342.50169,379.67351 330.55601,386.25218 325.79976,396.05504 L 325.64957,396.37548 L 324.38792,396.37548 L 324.38792,381.92648 L 309.16794,381.92648 L 309.16794,472.88583 L 324.93864,472.88583 L 324.93864,430.84068 C 324.93864,419.03519 327.24166,409.67292 331.78763,403.02416 C 336.70408,395.83475 344.02369,392.18997 353.54618,392.18997 C 367.97512,393.0811 373.6025,402.4634 373.6025,425.67393 L 373.6025,472.88583 L 389.37321,472.88583 L 389.37321,418.61462 C 389.37321,404.87658 386.07888,394.62311 379.59037,388.13464" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path58"/>
|
||||
<path d="M 533.222,420.44702 C 524.52058,416.4718 516.29979,412.72689 516.29979,403.99547 C 516.29979,396.81599 522.87843,392.18997 533.06179,392.18997 C 536.77666,392.18997 546.07887,394.27271 550.45461,396.5957 L 551.76634,383.16808 C 544.79719,381.06532 538.58904,379.67351 531.25943,379.67351 C 520.74563,379.67351 512.20441,382.65747 506.56701,388.29486 C 501.78074,393.0811 499.35755,399.51957 499.35755,407.43997 C 499.35755,421.45839 511.46344,427.37616 522.14746,432.60299 C 530.91898,436.87862 539.18983,440.93395 539.18983,448.81429 C 539.18983,452.37896 538.01829,455.36283 535.70526,457.67588 C 531.25943,462.12174 524.18013,462.60239 521.35643,462.60239 C 516.85052,462.60239 507.67848,460.66986 500.73937,456.45431 L 499.91829,470.32252 C 508.10904,474.55809 517.39122,475.11877 524.59067,475.11877 C 533.09183,475.11877 540.86203,472.70559 546.44936,468.29986 C 552.78767,463.30326 556.14208,455.94362 556.14208,446.9919 C 556.14208,430.90077 543.9661,425.3535 533.222,420.44702" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path62"/>
|
||||
<path d="M 611.11423,462.61241 C 607.20911,462.61241 603.6244,461.16044 601.01097,458.55708 C 597.92692,455.47298 596.3048,450.97714 596.3048,445.55997 L 596.3048,394.42292 L 620.66677,394.42292 L 620.66677,381.90646 L 596.3048,381.90646 L 596.3048,355.4017 L 580.52408,360.46832 L 580.52408,381.90646 L 559.76684,381.90646 L 559.76684,394.42292 L 580.52408,394.42292 L 580.52408,449.00455 C 580.52408,457.29537 582.747,463.71382 587.13276,468.08958 C 591.78887,472.75565 598.88818,475.11877 608.22043,475.11877 C 613.54742,475.11877 618.79431,473.68691 622.10866,472.74564 L 622.10866,459.71857 C 618.8644,461.61105 615.17957,462.61241 611.11423,462.61241" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path66"/>
|
||||
<path d="M 786.85488,338.61963 L 786.85488,394.38286 L 785.63328,394.38286 L 785.47307,394.12251 C 779.53527,384.52993 769.44203,379.67351 755.47369,379.67351 C 744.8097,379.67351 735.94807,383.11801 729.14915,389.91698 C 720.8883,398.17784 716.34234,410.88448 716.34234,425.68395 C 716.34234,442.11546 721.31887,456.51439 730.00027,465.18573 C 736.58891,471.78442 745.10008,475.11877 755.29346,475.11877 C 770.87391,475.11877 781.47782,467.46874 786.20403,460.29936 L 786.36424,460.04902 L 787.56581,460.04902 L 787.56581,472.8658 L 802.62558,472.8658 L 802.62558,338.61963 L 786.85488,338.61963 z M 786.85488,427.57643 C 786.85488,438.71102 783.04989,449.15475 776.67152,455.53306 C 771.98537,460.22926 766.12768,462.61241 759.25866,462.61241 C 753.08055,462.61241 747.71351,460.3294 743.32775,455.83347 C 735.5876,447.89304 733.08431,435.35664 733.28457,427.04571 C 733.03424,415.32032 736.34859,405.06684 742.36649,398.90875 C 746.73222,394.45296 752.35961,392.18997 759.07843,392.18997 C 766.01753,392.18997 771.95533,394.60308 776.71157,399.35936 C 783.16003,405.80784 786.85488,416.09128 786.85488,427.57643" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path70"/>
|
||||
<path d="M 472.51232,338.61963 L 472.51232,394.38286 L 471.29072,394.38286 L 471.13051,394.12251 C 465.1827,384.52993 455.08945,379.67351 441.13114,379.67351 C 430.45712,379.67351 421.5955,383.11801 414.80658,389.91698 C 406.54575,398.17784 401.98976,410.88448 401.98976,425.68395 C 401.98976,442.11546 406.97631,456.51439 415.64769,465.18573 C 422.23633,471.78442 430.74751,475.11877 440.95089,475.11877 C 456.52134,475.11877 467.12525,467.46874 471.85145,460.29936 L 472.01166,460.04902 L 473.22325,460.04902 L 473.22325,472.8658 L 488.27301,472.8658 L 488.27301,338.61963 L 472.51232,338.61963 z M 472.51232,427.57643 C 472.51232,438.71102 468.69731,449.15475 462.32896,455.53306 C 457.63279,460.22926 451.7751,462.61241 444.9161,462.61241 C 438.72798,462.61241 433.37095,460.3294 428.9852,455.83347 C 421.24503,447.89304 418.73173,435.35664 418.94201,427.04571 C 418.69168,415.32032 421.99602,405.06684 428.02393,398.90875 C 432.38966,394.45296 438.00704,392.18997 444.73587,392.18997 C 451.67497,392.18997 457.60275,394.60308 462.359,399.35936 C 468.80746,405.80784 472.51232,416.09128 472.51232,427.57643" style="fill:#007cc5;fill-rule:nonzero;stroke:none" id="path74"/>
|
||||
</g>
|
||||
</g>
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 70.5 33.5" style="enable-background:new 0 0 70.5 33.5;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#007DC5;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
<slices>
|
||||
</slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="33.5" width="70.5" x="-228.2" y="-213.4">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<g>
|
||||
<path class="st0" d="M26,33.5h7.4V14.8c0-1-0.4-2-1.1-2.6l-11-11C20.7,0.4,19.7,0,18.7,0H0v7.4h20.4c3.1,0,5.6,2.5,5.6,5.6V33.5z">
|
||||
</path>
|
||||
<path class="st0" d="M44.5,33.5h-7.4V14.8c0-1,0.4-2,1.1-2.6l11-11C49.9,0.4,50.8,0,51.8,0h18.7v7.4H50.1c-3.1,0-5.6,2.5-5.6,5.6
|
||||
V33.5z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 846 B |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 10 KiB |
52
auth/assets/custom-icons/icons/us_mobile.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<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"
|
||||
width="400px" height="400px" viewBox="1754.66 549.858 400 400" enable-background="new 1754.66 549.858 400 400"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g id="deeditor_bgCarrier">
|
||||
<rect id="dee_c_e" x="1754.66" y="549.858" fill="#1043B8" width="400" height="400"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#E6E7E8" d="M2137.105,669.97l-45.542,18.7c0,0,45.358-18.52,45.358-18.701h-131.485v2.541
|
||||
c-0.184,4.357-0.918,8.534-2.204,12.347c-1.652,5.446-4.224,10.531-7.713,14.888c-4.591,6.173-10.65,11.439-17.813,14.888
|
||||
c-0.184,0-0.367,0.182-0.367,0.182c-0.367,0.18-0.734,0.362-1.286,0.544h-0.184c-0.367,0.182-0.367,0.182-0.551,0.182h-0.184
|
||||
V669.97h-50.134c-0.918,0-1.836,0.726-1.836,1.634v7.262c1.469-1.999,3.856-3.269,6.611-3.269c1.469,0,2.938,0.364,4.04,1.09
|
||||
c0.184,0,0.367,0.182,0.551,0.182c0.551,0.362,0.918,0.726,1.285,0.906c1.47,1.091,2.755,2.362,3.856,3.813l2.571,3.995
|
||||
l5.142,8.17l-13.957,21.786c-0.734-0.362-1.285-0.542-2.203-1.088c-6.945-3.615-13.023-8.695-17.813-14.888
|
||||
c-3.319-4.537-5.921-9.559-7.713-14.888c-1.12-3.853-1.858-7.806-2.203-11.803c0-1.088-0.184-2.177-0.184-3.085h-130.751
|
||||
l44.991,18.701l-45.176-18.519c1.47,5.083,3.306,9.985,5.143,14.888l35.259,14.888h-28.465c2.571,5.083,5.326,10.167,8.447,14.888
|
||||
l60.785,14.888h-50.501c4.04,5.266,8.264,10.167,12.671,14.888l78.414,15.252h-62.621c6.428,5.446,13.223,10.349,20.385,14.888
|
||||
l82.638,14.889h-54.357c20.935,8.896,43.522,13.98,67.396,14.889h14.324c23.873-0.908,46.645-6.174,67.396-14.889h-54.357
|
||||
l82.821-14.889c7.162-4.541,13.957-9.622,20.384-14.888h-62.621l78.414-14.888c4.407-4.722,8.631-9.805,12.671-14.889h-50.5
|
||||
l60.784-14.89c2.938-4.901,5.877-9.803,8.447-14.888h-28.647l35.259-14.888C2133.984,679.955,2135.637,675.054,2137.105,669.97"/>
|
||||
<path fill="#99999B" d="M1951.63,685.402c3.854,0,6.979-3.088,6.979-6.899c0-3.812-3.124-6.898-6.979-6.898
|
||||
s-6.979,3.089-6.979,6.898C1944.651,682.314,1947.777,685.402,1951.63,685.402"/>
|
||||
<path fill="#E7E7E6" d="M2005.62,669.97h-0.184v2.541c-0.184,4.357-0.918,8.534-2.204,12.347
|
||||
c-1.652,5.446-4.224,10.531-7.713,14.888c-4.591,6.173-10.65,11.439-17.813,14.888c-0.184,0-0.367,0.182-0.367,0.182
|
||||
c-0.367,0.181-0.734,0.363-1.286,0.544h-0.184c-0.367,0.182-0.367,0.182-0.551,0.182h-0.184V669.97h-50.134
|
||||
c-0.918,0-1.836,0.726-1.836,1.634v7.262c1.469-1.999,3.856-3.269,6.611-3.269c1.469,0,2.938,0.364,4.04,1.09
|
||||
c0.184,0,0.367,0.182,0.551,0.182c0.551,0.362,0.918,0.726,1.285,0.906c1.47,1.091,2.755,2.362,3.856,3.813l2.571,3.995
|
||||
l5.142,8.17l-13.957,21.786c-0.734-0.362-1.285-0.542-2.203-1.088c-6.945-3.615-13.023-8.695-17.813-14.888
|
||||
c-3.319-4.537-5.921-9.559-7.713-14.888c-1.12-3.853-1.858-7.806-2.203-11.803c0-1.088-0.184-2.177-0.184-3.085h-130.751
|
||||
l44.991,18.701l1.469,0.544l1.286,0.908l134.424,99.316l134.424-99.134l1.285-0.908l1.47-0.544c0,0,45.358-18.519,45.358-18.701
|
||||
H2005.62z M1951.813,673.057c2.388,0,4.407,1.997,4.407,4.357c0,2.361-2.02,4.357-4.407,4.357s-4.407-1.997-4.407-4.357
|
||||
C1947.406,675.054,1949.426,673.057,1951.813,673.057"/>
|
||||
<path fill="#CED4D4" d="M2137.105,669.97l-45.542,18.7l-1.47,0.544l-1.285,0.908l-134.24,99.134l-134.607-99.132l-1.286-0.908
|
||||
l-1.469-0.544l-44.992-18.701c1.47,5.083,3.306,9.985,5.143,14.888l35.259,14.888h-28.465c2.571,5.083,5.326,10.167,8.447,14.888
|
||||
l60.785,14.888h-50.501c4.04,5.266,8.264,10.167,12.671,14.888l78.414,15.252h-62.621c6.428,5.446,13.223,10.349,20.385,14.888
|
||||
l82.638,14.889h-54.357c20.935,8.896,43.522,13.98,67.396,14.889h14.324c23.873-0.908,46.645-6.174,67.396-14.889h-54.357
|
||||
l82.821-14.889c7.162-4.541,13.957-9.622,20.384-14.888h-62.621l78.414-14.888c4.407-4.722,8.631-9.805,12.671-14.889h-50.5
|
||||
l60.784-14.89c2.938-4.901,5.877-9.803,8.447-14.888h-28.647l35.259-14.888C2133.984,679.955,2135.637,675.054,2137.105,669.97"/>
|
||||
<path fill="#0055A2" d="M1970.359,726.256h-74.741l17.263,29.412h86.678l17.262-29.412H1970.359z"/>
|
||||
<path fill="#CE2832" d="M1999.559,755.668h-29.199v49.93l16.16-27.596L1999.559,755.668z"/>
|
||||
<path fill="#F2F2F2" d="M1942.08,755.668v49.93l14.141,24.331l14.141-24.331v-49.93H1942.08z"/>
|
||||
<path fill="#CE2832" d="M1940.426,755.668h-27.545L1925.919,778l16.16,27.598v-49.93H1940.426z"/>
|
||||
<path fill="#FFFFFF" d="M1956.219,732.426l2.388,4.903l5.325,0.727l-3.855,3.813l0.918,5.266l-4.774-2.542l-4.959,2.542
|
||||
l1.103-5.266l-4.041-3.813l5.51-0.727L1956.219,732.426z M1984.684,732.426l2.387,4.903l5.51,0.727l-4.04,3.813l0.918,5.266
|
||||
l-4.774-2.542l-4.774,2.542l0.918-5.266l-3.856-3.813l5.325-0.727L1984.684,732.426z M1927.571,732.426l2.571,4.903l5.325,0.727
|
||||
l-3.856,3.813l0.919,5.266l-4.959-2.542l-4.774,2.542l0.918-5.266l-3.856-3.813l5.326-0.727L1927.571,732.426z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96">
|
||||
<g style="mix-blend-mode:difference">
|
||||
<path fill="white" fill-rule="evenodd" d="M47.77,1A47,47,0,1,1,1,48.23V48A46.87,46.87,0,0,1,47.71,1Zm0,9a38.12,38.12,0,0,1,19.13,71A82.23,82.23,0,0,0,69.84,54.5c2.44.69,3.82,1.08,6.27,1.87A128.54,128.54,0,0,0,66.2,36.09,81.28,81.28,0,0,1,53.06,54.25l7.84-.49c-6.75,12.12-16.27,18-28.33,19.71-2.75-13.71-.69-36,14.7-46.48l1.56,7.48c3-4.83,8.93-11.22,13.34-15.67a41.77,41.77,0,0,0-22.83-2.15c1.27,1.09,2.84,2.47,4.11,3.55C26.1,27.57,16.57,39.68,10.3,53.76A38.3,38.3,0,0,1,47.77,9.94Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 634 B |
5
auth/assets/custom-icons/icons/wargamingnet.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#df1b1a"/>
|
||||
<path d="M512 256c-141 0-255.7 114.2-255.7 255.7C256.3 653.3 370.5 768 512 768s255.7-115.3 255.7-256.3S653.5 256 512 256zm104.5 435.3c12.9-45 18.2-91.1 16.1-144.2 13.4 3.8 20.9 5.9 34.3 10.2-15.5-39.1-32.2-74.5-54.2-110.4-19.3 37-45.6 71.8-71.8 99.2l42.9-2.7c-37 65.9-89 98.1-154.9 107.2-15-74.5-3.8-196.2 80.4-253.1l8.6 40.7c16.6-26.3 48.8-61.1 72.9-85.2-38.1-15-82-19.8-124.9-11.8 7 5.9 15.5 13.4 22.5 19.3-94.9 40.2-146.9 106.2-181.2 182.8-1.6-10.2-2.1-20.9-2.1-31.1 0-114.7 92.8-207.5 207-207.5 114.7 0 207.5 92.8 207.5 207.5-.1 76.2-41.4 143.2-103.1 179.1z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 865 B |
@@ -435,8 +435,6 @@
|
||||
"customEndpoint": "متصل بـ{endpoint}",
|
||||
"pinText": "ثبت",
|
||||
"unpinText": "ألغِ التثبيت",
|
||||
"pinnedCodeMessage": "ثُبِّت {code}",
|
||||
"unpinnedCodeMessage": "أُلغِي تثبيت {code}",
|
||||
"tags": "الأوسمة",
|
||||
"createNewTag": "أنشيء وسم جديد",
|
||||
"tag": "وسم",
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
"hintForMobile": "Натиснете продължително код, за да го редактирате или премахнете.",
|
||||
"hintForDesktop": "Натиснете десен бутон върху код, за да го редактирате или премахнете.",
|
||||
"scan": "Сканиране",
|
||||
"scanACode": "Скениране на код",
|
||||
"scanACode": "Сканиране на код",
|
||||
"verify": "Потвърждаване",
|
||||
"verifyEmail": "Потвърдете имейла",
|
||||
"enterCodeHint": "Въведете 6-цифрения код от\nВашето приложение за удостоверяване",
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Двуфакторно удостоверяване",
|
||||
"passkeyAuthTitle": "Удостоверяване с ключ за парола",
|
||||
"verifyPasskey": "Потвърдете ключ за парола",
|
||||
"loginWithTOTP": "Влизане с еднократен код",
|
||||
"recoverAccount": "Възстановяване на акаунт",
|
||||
"enterRecoveryKeyHint": "Въведете Вашия ключ за възстановяване",
|
||||
"recover": "Възстановяване",
|
||||
@@ -199,7 +200,7 @@
|
||||
"sorryUnableToGenCode": "За съжаление не може да се генерира код за {issuerName}",
|
||||
"noResult": "Няма резултати",
|
||||
"addCode": "Добавяне на код",
|
||||
"scanAQrCode": "Скениране на QR код",
|
||||
"scanAQrCode": "Сканиране на QR код",
|
||||
"enterDetailsManually": "Въведете подробности ръчно",
|
||||
"edit": "Редактиране",
|
||||
"share": "Споделяне",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Персонализирано",
|
||||
"editOrder": "Промяна на подредбата",
|
||||
"mostFrequentlyUsed": "Често използвани",
|
||||
"mostRecentlyUsed": "Последно използвани",
|
||||
"activeSessions": "Активни сесии",
|
||||
"somethingWentWrongPleaseTryAgain": "Нещо се обърка, моля опитайте отново",
|
||||
"thisWillLogYouOutOfThisDevice": "Това ще Ви изкара от профила на това устройство!",
|
||||
@@ -444,10 +449,11 @@
|
||||
"invalidEndpointMessage": "За съжаление въведената от Вас крайна точка е невалидна. Моля, въведете валидна крайна точка и опитайте отново.",
|
||||
"endpointUpdatedMessage": "Крайната точка е актуализирана успешно",
|
||||
"customEndpoint": "Свързан към {endpoint}",
|
||||
"pinText": "ПИН код",
|
||||
"pinText": "Закачане",
|
||||
"unpinText": "Откачане",
|
||||
"pinnedCodeMessage": "{code} е закачен",
|
||||
"unpinnedCodeMessage": "{code} е откачен",
|
||||
"pinned": "Закачен",
|
||||
"tags": "Етикети",
|
||||
"createNewTag": "Създаване на етикет",
|
||||
"tag": "Етикет",
|
||||
|
||||
@@ -446,8 +446,6 @@
|
||||
"customEndpoint": "Connectat a {endpoint}",
|
||||
"pinText": "Fixa",
|
||||
"unpinText": "Desfixa",
|
||||
"pinnedCodeMessage": "{code} fixat",
|
||||
"unpinnedCodeMessage": "{code} deixat de fixar",
|
||||
"tags": "Etiquetes",
|
||||
"createNewTag": "Crea una nova etiqueta",
|
||||
"tag": "Etiqueta",
|
||||
|
||||
@@ -446,8 +446,6 @@
|
||||
"customEndpoint": "Forbindelse oprettet til {endpoint}",
|
||||
"pinText": "Fastgør",
|
||||
"unpinText": "Frigør",
|
||||
"pinnedCodeMessage": "{code} er blevet fastgjort",
|
||||
"unpinnedCodeMessage": "{code} er blevet frigjort",
|
||||
"tags": "Tags",
|
||||
"createNewTag": "Opret nyt tag",
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -444,8 +444,6 @@
|
||||
"customEndpoint": "Mit {endpoint} verbunden",
|
||||
"pinText": "Anpinnen",
|
||||
"unpinText": "Lösen",
|
||||
"pinnedCodeMessage": "{code} wurde angepinnt",
|
||||
"unpinnedCodeMessage": "{code} wurde Losgelöst",
|
||||
"tags": "Tags",
|
||||
"createNewTag": "Neuen Tag erstellen",
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Αυθεντικοποίηση δύο παραγόντων",
|
||||
"passkeyAuthTitle": "Επιβεβαίωση κλειδιού πρόσβασης",
|
||||
"verifyPasskey": "Επιβεβαίωση κλειδιού πρόσβασης",
|
||||
"loginWithTOTP": "Είσοδος με TOTP",
|
||||
"recoverAccount": "Ανάκτηση λογαριασμού",
|
||||
"enterRecoveryKeyHint": "Εισάγετε το κλειδί ανάκτησης σας",
|
||||
"recover": "Ανάκτηση",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Προσαρμοσμένο",
|
||||
"editOrder": "Επεξεργασία σειράς",
|
||||
"mostFrequentlyUsed": "Συχνά χρησιμοποιούμενο",
|
||||
"mostRecentlyUsed": "Πρόσφατα χρησιμοποιούμενο",
|
||||
"activeSessions": "Ενεργές συνεδρίες",
|
||||
"somethingWentWrongPleaseTryAgain": "Κάτι πήγε στραβά, παρακαλώ προσπαθήστε ξανά",
|
||||
"thisWillLogYouOutOfThisDevice": "Αυτό θα σας αποσυνδέσει από αυτή τη συσκευή!",
|
||||
@@ -446,8 +451,9 @@
|
||||
"customEndpoint": "Συνδεδεμένο στο {endpoint}",
|
||||
"pinText": "Καρφίτσωμα",
|
||||
"unpinText": "Ξεκαρφίτσωμα",
|
||||
"pinnedCodeMessage": "Το {code} καρφιτσώθηκε",
|
||||
"unpinnedCodeMessage": "Το {code} ξεκαρφιτσώθηκε",
|
||||
"pinnedCodeMessage": "{code} έχει καρφιτσωθεί",
|
||||
"unpinnedCodeMessage": "Το {code} έχει ξεκαρφιτσωθεί",
|
||||
"pinned": "Καρφιτσωμένο",
|
||||
"tags": "Ετικέτες",
|
||||
"createNewTag": "Δημιουργία Νέας Ετικέτας",
|
||||
"tag": "Ετικέτα",
|
||||
|
||||
@@ -258,6 +258,10 @@
|
||||
"areYouSureYouWantToLogout": "Are you sure you want to logout?",
|
||||
"yesLogout": "Yes, logout",
|
||||
"exit": "Exit",
|
||||
"theme": "Theme",
|
||||
"lightTheme": "Light",
|
||||
"darkTheme": "Dark",
|
||||
"systemTheme": "System",
|
||||
"verifyingRecoveryKey": "Verifying recovery key...",
|
||||
"recoveryKeyVerified": "Recovery key verified",
|
||||
"recoveryKeySuccessBody": "Great! Your recovery key is valid. Thank you for verifying.\n\nPlease remember to keep your recovery key safely backed up.",
|
||||
@@ -491,5 +495,12 @@
|
||||
"appLockNotEnabled": "App lock not enabled",
|
||||
"appLockNotEnabledDescription": "Please enable app lock from Security > App Lock",
|
||||
"authToViewPasskey": "Please authenticate to view passkey",
|
||||
"appLockOfflineModeWarning": "You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data."
|
||||
"appLockOfflineModeWarning": "You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data.",
|
||||
"duplicateCodes": "Duplicate codes",
|
||||
"noDuplicates": "✨ No duplicates",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "You've no duplicate codes that can be cleared",
|
||||
"deduplicateCodes": "Deduplicate codes",
|
||||
"deselectAll": "Deselect all",
|
||||
"selectAll": "Select all",
|
||||
"deleteDuplicates": "Delete duplicates"
|
||||
}
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Personalizado",
|
||||
"editOrder": "Editar orden",
|
||||
"mostFrequentlyUsed": "Usados frecuentemente",
|
||||
"mostRecentlyUsed": "Usados recientemente",
|
||||
"activeSessions": "Sesiones activas",
|
||||
"somethingWentWrongPleaseTryAgain": "Algo ha ido mal, por favor, inténtelo de nuevo",
|
||||
"thisWillLogYouOutOfThisDevice": "¡Esto cerrará la sesión de este dispositivo!",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Desanclar",
|
||||
"pinnedCodeMessage": "{code} ha sido anclado",
|
||||
"unpinnedCodeMessage": "{code} ha sido desanclado",
|
||||
"pinned": "Anclado",
|
||||
"tags": "Etiquetas",
|
||||
"createNewTag": "Crear Nueva Etiqueta",
|
||||
"tag": "Etiqueta",
|
||||
@@ -485,5 +490,12 @@
|
||||
"appLockNotEnabled": "Bloqueo de aplicación no activado",
|
||||
"appLockNotEnabledDescription": "Por favor, activa el bloqueo de aplicación desde Seguridad > Bloqueo de aplicación",
|
||||
"authToViewPasskey": "Por favor, autentícate para ver tu clave de acceso",
|
||||
"appLockOfflineModeWarning": "Has elegido proceder sin copia de seguridad. Si olvidas el código de desbloqueo de la aplicación, se bloqueará el acceso a sus datos."
|
||||
"appLockOfflineModeWarning": "Has elegido proceder sin copia de seguridad. Si olvidas el código de desbloqueo de la aplicación, se bloqueará el acceso a sus datos.",
|
||||
"duplicateCodes": "Duplicar códigos",
|
||||
"noDuplicates": "✨ No hay duplicados",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "No tienes códigos duplicados que se puedan borrar",
|
||||
"deduplicateCodes": "Desduplicar códigos",
|
||||
"deselectAll": "Deseleccionar todo",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"deleteDuplicates": "Eliminar duplicados"
|
||||
}
|
||||
@@ -401,8 +401,6 @@
|
||||
"customEndpoint": "متصل شده به {endpoint}",
|
||||
"pinText": "پین",
|
||||
"unpinText": "حذف پین",
|
||||
"pinnedCodeMessage": "{code} پین شد",
|
||||
"unpinnedCodeMessage": "{code} از پین حذف شد",
|
||||
"tags": "برچسبها",
|
||||
"createNewTag": "ایجاد برچسب جدید",
|
||||
"tag": "برچسب",
|
||||
|
||||
@@ -445,8 +445,6 @@
|
||||
"customEndpoint": "Connecté à {endpoint}",
|
||||
"pinText": "Épingler",
|
||||
"unpinText": "Désépingler",
|
||||
"pinnedCodeMessage": "{code} a été épinglé",
|
||||
"unpinnedCodeMessage": "{code} a été désépinglé",
|
||||
"tags": "Tags",
|
||||
"createNewTag": "Créer un nouveau tag",
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Autentikasi dua langkah",
|
||||
"passkeyAuthTitle": "Verifikasi passkey",
|
||||
"verifyPasskey": "Verifikasi passkey",
|
||||
"loginWithTOTP": "Login menggunakan TOTP",
|
||||
"recoverAccount": "Pulihkan akun",
|
||||
"enterRecoveryKeyHint": "Masukkan kunci pemulihanmu",
|
||||
"recover": "Pulihkan",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Kustom",
|
||||
"editOrder": "Ubah pesanan",
|
||||
"mostFrequentlyUsed": "Sering digunakan",
|
||||
"mostRecentlyUsed": "Baru digunakan",
|
||||
"activeSessions": "Sesi aktif",
|
||||
"somethingWentWrongPleaseTryAgain": "Ada yang salah. Mohon coba kembali",
|
||||
"thisWillLogYouOutOfThisDevice": "Langkah ini akan mengeluarkan Anda dari gawai ini!",
|
||||
@@ -446,8 +451,6 @@
|
||||
"customEndpoint": "Terkoneksi ke {endpoint}",
|
||||
"pinText": "Sematkan",
|
||||
"unpinText": "Awasematkan",
|
||||
"pinnedCodeMessage": "{code} telah disematkan",
|
||||
"unpinnedCodeMessage": "{code} telah diawasematkan",
|
||||
"tags": "Tanda",
|
||||
"createNewTag": "Buat Tanda Baru",
|
||||
"tag": "Tanda",
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Autenticazione a due fattori",
|
||||
"passkeyAuthTitle": "Verifica della passkey",
|
||||
"verifyPasskey": "Verifica passkey",
|
||||
"loginWithTOTP": "Login con TOTP",
|
||||
"recoverAccount": "Recupera account",
|
||||
"enterRecoveryKeyHint": "Inserisci la tua chiave di recupero",
|
||||
"recover": "Recupera",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Personalizzato",
|
||||
"editOrder": "Modifica ordine",
|
||||
"mostFrequentlyUsed": "Utilizzato di frequente",
|
||||
"mostRecentlyUsed": "Utilizzato di recente",
|
||||
"activeSessions": "Sessioni attive",
|
||||
"somethingWentWrongPleaseTryAgain": "Qualcosa è andato storto, per favore riprova",
|
||||
"thisWillLogYouOutOfThisDevice": "Questo ti disconnetterà da questo dispositivo!",
|
||||
@@ -448,6 +453,7 @@
|
||||
"unpinText": "Sgancia",
|
||||
"pinnedCodeMessage": "{code} è stato fissato",
|
||||
"unpinnedCodeMessage": "{code} è stato sganciato",
|
||||
"pinned": "Fissato",
|
||||
"tags": "Tag",
|
||||
"createNewTag": "Crea un nuovo tag",
|
||||
"tag": "Tag",
|
||||
@@ -484,5 +490,12 @@
|
||||
"appLockNotEnabled": "Blocco app non abilitato",
|
||||
"appLockNotEnabledDescription": "Si prega di abilitare il blocco dell'app da Sicurezza > Blocco App",
|
||||
"authToViewPasskey": "Autenticati per visualizzare le tue passkey",
|
||||
"appLockOfflineModeWarning": "Hai scelto di procedere senza backup. Se dimentichi il tuo codice di blocco dell'app, non potrai più accedere ai tuoi dati."
|
||||
"appLockOfflineModeWarning": "Hai scelto di procedere senza backup. Se dimentichi il tuo codice di blocco dell'app, non potrai più accedere ai tuoi dati.",
|
||||
"duplicateCodes": "Codici duplicati",
|
||||
"noDuplicates": "✨ Nessun doppione",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Non ci sono codici duplicati che possono essere cancellati",
|
||||
"deduplicateCodes": "Codici deduplicati",
|
||||
"deselectAll": "Deselezionare tutti",
|
||||
"selectAll": "Seleziona tutti",
|
||||
"deleteDuplicates": "Elimina i duplicati"
|
||||
}
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "2 要素認証",
|
||||
"passkeyAuthTitle": "パスキー認証",
|
||||
"verifyPasskey": "パスキーの認証",
|
||||
"loginWithTOTP": "TOTPでログイン",
|
||||
"recoverAccount": "アカウントを回復",
|
||||
"enterRecoveryKeyHint": "回復キーを入力",
|
||||
"recover": "回復",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "カスタム",
|
||||
"editOrder": "並べ替え",
|
||||
"mostFrequentlyUsed": "よく使う",
|
||||
"mostRecentlyUsed": "最近使った",
|
||||
"activeSessions": "アクティブセッション",
|
||||
"somethingWentWrongPleaseTryAgain": "問題が発生しました、再試行してください",
|
||||
"thisWillLogYouOutOfThisDevice": "このデバイスからログアウトします!",
|
||||
@@ -446,8 +451,9 @@
|
||||
"customEndpoint": "{endpoint} に接続しました",
|
||||
"pinText": "固定",
|
||||
"unpinText": "固定を解除",
|
||||
"pinnedCodeMessage": "{code} を固定しました",
|
||||
"unpinnedCodeMessage": "{code} の固定が解除されました",
|
||||
"pinnedCodeMessage": "{code}がピン留めされました",
|
||||
"unpinnedCodeMessage": "{code}のピン留めが解除されました",
|
||||
"pinned": "ピン留め",
|
||||
"tags": "タグ",
|
||||
"createNewTag": "新しいタグの作成",
|
||||
"tag": "タグ",
|
||||
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "사용자 정의",
|
||||
"editOrder": "순서 변경",
|
||||
"mostFrequentlyUsed": "자주 사용됨",
|
||||
"mostRecentlyUsed": "최근에 사용됨",
|
||||
"activeSessions": "활성화된 세션",
|
||||
"somethingWentWrongPleaseTryAgain": "뭔가 잘못됐습니다, 다시 시도해주세요",
|
||||
"thisWillLogYouOutOfThisDevice": "이 작업을 하시면 기기에서 로그아웃하게 됩니다!",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "핀 해제",
|
||||
"pinnedCodeMessage": "{code}가 핀 되었습니다.",
|
||||
"unpinnedCodeMessage": "{code}의 핀이 해제되었습니다.",
|
||||
"pinned": "고정됨",
|
||||
"tags": "태그",
|
||||
"createNewTag": "새 태그 만들기",
|
||||
"tag": "태그",
|
||||
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Pasirinktinis",
|
||||
"editOrder": "Redaguoti tvarką",
|
||||
"mostFrequentlyUsed": "Dažniausiai naudojamą",
|
||||
"mostRecentlyUsed": "Neseniai naudotą",
|
||||
"activeSessions": "Aktyvūs seansai",
|
||||
"somethingWentWrongPleaseTryAgain": "Kažkas nutiko ne taip. Bandykite dar kartą.",
|
||||
"thisWillLogYouOutOfThisDevice": "Tai jus atjungs nuo šio įrenginio.",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Atsegti",
|
||||
"pinnedCodeMessage": "{code} buvo prisegtas",
|
||||
"unpinnedCodeMessage": "{code} buvo atsegtas",
|
||||
"pinned": "Prisegta",
|
||||
"tags": "Žymės",
|
||||
"createNewTag": "Kurti naują žymę",
|
||||
"tag": "Žymė",
|
||||
|
||||
1
auth/lib/l10n/arb/app_ml.arb
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Tweestapsverificatie",
|
||||
"passkeyAuthTitle": "Passkey verificatie",
|
||||
"verifyPasskey": "Bevestig passkey",
|
||||
"loginWithTOTP": "Inloggen met TOTP",
|
||||
"recoverAccount": "Account herstellen",
|
||||
"enterRecoveryKeyHint": "Voer je herstelsleutel in",
|
||||
"recover": "Herstellen",
|
||||
@@ -327,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Aangepast",
|
||||
"editOrder": "Volgorde wijzigen",
|
||||
"mostFrequentlyUsed": "Vaak gebruikt",
|
||||
"mostRecentlyUsed": "Recent gebruikt",
|
||||
"activeSessions": "Actieve sessies",
|
||||
"somethingWentWrongPleaseTryAgain": "Er is iets fout gegaan, probeer het opnieuw",
|
||||
"thisWillLogYouOutOfThisDevice": "Dit zal je uitloggen van dit apparaat!",
|
||||
@@ -448,6 +453,7 @@
|
||||
"unpinText": "Losmaken",
|
||||
"pinnedCodeMessage": "{code} is vastgezet",
|
||||
"unpinnedCodeMessage": "{code} is losgemaakt",
|
||||
"pinned": "Vastgezet",
|
||||
"tags": "Labels",
|
||||
"createNewTag": "Nieuw label maken",
|
||||
"tag": "Label",
|
||||
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Niestandardowe",
|
||||
"editOrder": "Zmień kolejność",
|
||||
"mostFrequentlyUsed": "Często używane",
|
||||
"mostRecentlyUsed": "Ostatnio używane",
|
||||
"activeSessions": "Aktywne sesje",
|
||||
"somethingWentWrongPleaseTryAgain": "Coś poszło nie tak, spróbuj ponownie",
|
||||
"thisWillLogYouOutOfThisDevice": "To wyloguje Cię z tego urządzenia!",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Odepnij",
|
||||
"pinnedCodeMessage": "Przypięto {code}",
|
||||
"unpinnedCodeMessage": "Odpięto {code}",
|
||||
"pinned": "Przypięte",
|
||||
"tags": "Etykiety",
|
||||
"createNewTag": "Utwórz nową etykietę",
|
||||
"tag": "Etykieta",
|
||||
@@ -485,5 +490,12 @@
|
||||
"appLockNotEnabled": "Blokada aplikacji nie jest włączona",
|
||||
"appLockNotEnabledDescription": "Prosimy włączyć blokadę aplikacji z Zabezpieczenia > Blokada aplikacji",
|
||||
"authToViewPasskey": "Prosimy uwierzytelnić się, aby wyświetlić klucz dostępu",
|
||||
"appLockOfflineModeWarning": "Wybrano kontynuowanie bez kopii zapasowych. Jeśli zapomnisz blokady aplikacji, utracisz dostęp do swoich danych."
|
||||
"appLockOfflineModeWarning": "Wybrano kontynuowanie bez kopii zapasowych. Jeśli zapomnisz blokady aplikacji, utracisz dostęp do swoich danych.",
|
||||
"duplicateCodes": "Duplikuj kody",
|
||||
"noDuplicates": "✨ Brak duplikatów",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Nie masz duplikatów kodów, które mogą być wyczyszczone",
|
||||
"deduplicateCodes": "Deduplikuj kody",
|
||||
"deselectAll": "Odznacz wszystko",
|
||||
"selectAll": "Zaznacz wszystko",
|
||||
"deleteDuplicates": "Usuń duplikaty"
|
||||
}
|
||||
@@ -132,7 +132,7 @@
|
||||
"general": "Geral",
|
||||
"settings": "Ajustes",
|
||||
"copied": "Copiado",
|
||||
"pleaseTryAgain": "Tente de novo",
|
||||
"pleaseTryAgain": "Tente novamente",
|
||||
"existingUser": "Usuário existente",
|
||||
"newUser": "Novo no Ente",
|
||||
"delete": "Excluir",
|
||||
@@ -142,7 +142,7 @@
|
||||
"suggestFeatures": "Sugerir recursos",
|
||||
"faq": "Perguntas frequentes",
|
||||
"somethingWentWrongMessage": "Algo deu errado. Tente outra vez",
|
||||
"leaveFamily": "Sair da família",
|
||||
"leaveFamily": "Sair do plano familiar",
|
||||
"leaveFamilyMessage": "Deseja mesmo sair do plano familiar?",
|
||||
"inFamilyPlanMessage": "Você está em um plano familiar!",
|
||||
"hintForMobile": "Pressione em um código para editar ou excluir.",
|
||||
@@ -271,7 +271,7 @@
|
||||
"recoveryKeyVerifyReason": "Sua chave de recuperação é a única maneira de recuperar suas fotos se você esqueceu sua senha. Você pode encontrar sua chave de recuperação em Opções > Conta.\n\nInsira sua chave de recuperação aqui para verificar se você a salvou corretamente.",
|
||||
"confirmYourRecoveryKey": "Confirme sua chave de recuperação",
|
||||
"confirm": "Confirmar",
|
||||
"emailYourLogs": "Enviar logs por e-mail",
|
||||
"emailYourLogs": "Enviar registros por e-mail",
|
||||
"pleaseSendTheLogsTo": "Envie os logs para \n{toEmail}",
|
||||
"copyEmailAddress": "Copiar endereço de e-mail",
|
||||
"exportLogs": "Exportar logs",
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Personalizado",
|
||||
"editOrder": "Editar ordem",
|
||||
"mostFrequentlyUsed": "Usado com frequência",
|
||||
"mostRecentlyUsed": "Usado recentemente",
|
||||
"activeSessions": "Sessões ativas",
|
||||
"somethingWentWrongPleaseTryAgain": "Algo deu errado. Tente outra vez",
|
||||
"thisWillLogYouOutOfThisDevice": "Isso fará com que você saia deste dispositivo!",
|
||||
@@ -337,7 +341,7 @@
|
||||
"thisDevice": "Esse dispositivo",
|
||||
"toResetVerifyEmail": "Para redefinir sua senha, verifique seu e-mail primeiramente.",
|
||||
"thisEmailIsAlreadyInUse": "Este e-mail já está em uso",
|
||||
"verificationFailedPleaseTryAgain": "Falha na verificação. Tente novamente",
|
||||
"verificationFailedPleaseTryAgain": "Falhou na verificação. Tente novamente",
|
||||
"yourVerificationCodeHasExpired": "Seu código de verificação expirou",
|
||||
"incorrectCode": "Código incorreto",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "O código inserido está incorreto",
|
||||
@@ -354,7 +358,7 @@
|
||||
"plainText": "Texto simples",
|
||||
"passwordToEncryptExport": "Senha para criptografar a exportação",
|
||||
"export": "Exportar",
|
||||
"useOffline": "Usar sem backups",
|
||||
"useOffline": "Usar sem cópia de segurança",
|
||||
"signInToBackup": "Entre para fazer backup de seus códigos",
|
||||
"singIn": "Entrar",
|
||||
"sigInBackupReminder": "Exporte seus códigos para garantir que você tenha uma cópia para restaurar.",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Desafixar",
|
||||
"pinnedCodeMessage": "{code} foi fixado",
|
||||
"unpinnedCodeMessage": "{code} foi desafixado",
|
||||
"pinned": "Fixado",
|
||||
"tags": "Etiquetas",
|
||||
"createNewTag": "Criar nova etiqueta",
|
||||
"tag": "Etiqueta",
|
||||
@@ -485,5 +490,12 @@
|
||||
"appLockNotEnabled": "Bloqueio de aplicativo não ativado",
|
||||
"appLockNotEnabledDescription": "Ative o bloqueio de aplicativo em Segurança > Bloqueio de aplicativo",
|
||||
"authToViewPasskey": "Autentique para ver a sua chave de acesso",
|
||||
"appLockOfflineModeWarning": "Você prosseguiu sem cópias de segurança. Caso, se esqueça de seu aplicativo de bloqueio, você não poderá mais acessar seus dados."
|
||||
"appLockOfflineModeWarning": "Você prosseguiu sem cópias de segurança. Caso, se esqueça de seu aplicativo de bloqueio, você não poderá mais acessar seus dados.",
|
||||
"duplicateCodes": "Duplicar códigos",
|
||||
"noDuplicates": "✨ Sem duplicados",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Você não possui códigos duplicados para limpar",
|
||||
"deduplicateCodes": "Desduplicar códigos",
|
||||
"deselectAll": "Deselecionar tudo",
|
||||
"selectAll": "Selecionar tudo",
|
||||
"deleteDuplicates": "Excluir duplicados"
|
||||
}
|
||||
@@ -446,8 +446,6 @@
|
||||
"customEndpoint": "Подключено к {endpoint}",
|
||||
"pinText": "Прикрепить",
|
||||
"unpinText": "Открепить",
|
||||
"pinnedCodeMessage": "{code} прикреплен",
|
||||
"unpinnedCodeMessage": "{code} откреплен",
|
||||
"tags": "Метки",
|
||||
"createNewTag": "Создать новую метку",
|
||||
"tag": "Метка",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"timeBasedKeyType": "Na základe času (TOTP)",
|
||||
"counterBasedKeyType": "Na základe počítadla (HOTP)",
|
||||
"saveAction": "Uložiť",
|
||||
"nextTotpTitle": "ďalej",
|
||||
"nextTotpTitle": "ďalší",
|
||||
"deleteCodeTitle": "Odstrániť položku?",
|
||||
"deleteCodeMessage": "Naozaj chcete odstrániť položku? Táto akcia je nezvratná.",
|
||||
"trashCode": "Odstrániť kód?",
|
||||
@@ -156,6 +156,7 @@
|
||||
"twoFactorAuthTitle": "Dvojfaktorové overovanie",
|
||||
"passkeyAuthTitle": "Overenie pomocou passkey",
|
||||
"verifyPasskey": "Overiť passkey",
|
||||
"loginWithTOTP": "Prihlásenie pomocou TOTP",
|
||||
"recoverAccount": "Obnoviť účet",
|
||||
"enterRecoveryKeyHint": "Vložte váš kód pre obnovenie",
|
||||
"recover": "Obnoviť",
|
||||
@@ -446,8 +447,6 @@
|
||||
"customEndpoint": "Pripojený k endpointu {endpoint}",
|
||||
"pinText": "Pripnúť",
|
||||
"unpinText": "Odopnúť",
|
||||
"pinnedCodeMessage": "{code} bol pripnutý",
|
||||
"unpinnedCodeMessage": "{code} bol odopnutý",
|
||||
"tags": "Tagy",
|
||||
"createNewTag": "Vytvoriť nový tag",
|
||||
"tag": "Tag",
|
||||
|
||||
@@ -327,6 +327,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"mostFrequentlyUsed": "Pogosto uporabljeni",
|
||||
"mostRecentlyUsed": "Nedavno uporabljeno",
|
||||
"activeSessions": "Aktivne seje",
|
||||
"somethingWentWrongPleaseTryAgain": "Nekaj je šlo narobe, prosimo poizkusite znova.",
|
||||
"thisWillLogYouOutOfThisDevice": "To vas bo odjavilo iz te naprave!",
|
||||
@@ -446,8 +448,7 @@
|
||||
"customEndpoint": "Povezano na {endpoint}",
|
||||
"pinText": "Pripni",
|
||||
"unpinText": "Odpni",
|
||||
"pinnedCodeMessage": "{code} je bila pripeta",
|
||||
"unpinnedCodeMessage": "{code} je bila odpeta",
|
||||
"pinned": "Pripeto",
|
||||
"tags": "Oznake",
|
||||
"createNewTag": "Ustvari novo oznako",
|
||||
"tag": "Oznaka",
|
||||
|
||||
@@ -446,8 +446,6 @@
|
||||
"customEndpoint": "Bağlandı: {endpoint}",
|
||||
"pinText": "Sabitle",
|
||||
"unpinText": "Sabitlemeyi kaldır",
|
||||
"pinnedCodeMessage": "{code} sabitlendi",
|
||||
"unpinnedCodeMessage": "{code} sabitlemesi kaldırıldı",
|
||||
"tags": "Etiketler",
|
||||
"createNewTag": "Yeni etiket oluştur",
|
||||
"tag": "Etiket",
|
||||
|
||||
@@ -115,14 +115,14 @@
|
||||
"importCodeDelimiterInfo": "Коди можуть бути розділені комою або новим рядком",
|
||||
"selectFile": "Вибрати файл",
|
||||
"emailVerificationToggle": "Підтвердження адреси електронної пошти",
|
||||
"emailVerificationEnableWarning": "Щоб уникнути блокування доступу до свого облікового запису, обов’язково збережіть копію двофакторної аутентифікації до своєї електронної пошти за межами Ente Auth, перш ніж увімкнути перевірку електронної пошти.",
|
||||
"authToChangeEmailVerificationSetting": "Будь ласка, пройдіть аутентифікацію, щоб змінити перевірку адреси електронної пошти",
|
||||
"emailVerificationEnableWarning": "Щоб уникнути блокування доступу до свого облікового запису, обов’язково збережіть копію двоетапної автентифікації до своєї електронної пошти за межами Ente Auth, перш ніж увімкнути перевірку електронної пошти.",
|
||||
"authToChangeEmailVerificationSetting": "Будь ласка, пройдіть автентифікацію, щоб змінити перевірку адреси електронної пошти",
|
||||
"authenticateGeneric": "Будь ласка, авторизуйтеся",
|
||||
"authToViewYourRecoveryKey": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваш ключ відновлення",
|
||||
"authToChangeYourEmail": "Будь ласка, пройдіть аутентифікацію, щоб змінити адресу електронної пошти",
|
||||
"authToChangeYourPassword": "Будь ласка, пройдіть аутентифікацію, щоб змінити ваш пароль",
|
||||
"authToViewSecrets": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваші секретні коди",
|
||||
"authToInitiateSignIn": "Будь ласка, пройдіть аутентифікацію, щоб розпочати вхід для резервного копіювання.",
|
||||
"authToViewYourRecoveryKey": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваш ключ відновлення",
|
||||
"authToChangeYourEmail": "Будь ласка, пройдіть автентифікацію, щоб змінити адресу електронної пошти",
|
||||
"authToChangeYourPassword": "Будь ласка, пройдіть автентифікацію, щоб змінити ваш пароль",
|
||||
"authToViewSecrets": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваші секретні коди",
|
||||
"authToInitiateSignIn": "Будь ласка, пройдіть автентифікацію, щоб розпочати вхід для резервного копіювання.",
|
||||
"ok": "Ок",
|
||||
"cancel": "Скасувати",
|
||||
"yes": "Так",
|
||||
@@ -153,7 +153,7 @@
|
||||
"verifyEmail": "Підтвердити електронну адресу",
|
||||
"enterCodeHint": "Введіть нижче шестизначний код із застосунку для автентифікації",
|
||||
"lostDeviceTitle": "Загубили пристрій?",
|
||||
"twoFactorAuthTitle": "Двофакторна аутентифікація",
|
||||
"twoFactorAuthTitle": "Двоетапна автентифікація",
|
||||
"passkeyAuthTitle": "Перевірка секретного ключа",
|
||||
"verifyPasskey": "Підтвердження секретного ключа",
|
||||
"loginWithTOTP": "Увійти за допомогою TOTP",
|
||||
@@ -194,7 +194,7 @@
|
||||
"authToChangeLockscreenSetting": "Будь ласка, авторизуйтесь для зміни налаштувань екрану блокування",
|
||||
"deviceLockEnablePreSteps": "Для увімкнення блокування програми, будь ласка, налаштуйте пароль пристрою або блокування екрана в системних налаштуваннях.",
|
||||
"viewActiveSessions": "Показати активні сеанси",
|
||||
"authToViewYourActiveSessions": "Будь ласка, пройдіть аутентифікацію, щоб переглянути ваші активні сеанси",
|
||||
"authToViewYourActiveSessions": "Будь ласка, пройдіть автентифікацію, щоб переглянути ваші активні сеанси",
|
||||
"searchHint": "Пошук...",
|
||||
"search": "Пошук",
|
||||
"sorryUnableToGenCode": "Вибачте, не вдалося створити код для {issuerName}",
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Власні",
|
||||
"editOrder": "Змінити порядок",
|
||||
"mostFrequentlyUsed": "Часто використовувані",
|
||||
"mostRecentlyUsed": "Нещодавно використані",
|
||||
"activeSessions": "Активні сеанси",
|
||||
"somethingWentWrongPleaseTryAgain": "Щось пішло не так, спробуйте, будь ласка, знову",
|
||||
"thisWillLogYouOutOfThisDevice": "Це призведе до виходу на цьому пристрої!",
|
||||
@@ -342,9 +346,9 @@
|
||||
"incorrectCode": "Невірний код",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Вибачте, але введений вами код є невірним",
|
||||
"emailChangedTo": "Адресу електронної пошти змінено на {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Аутентифікація не пройдена. Будь ласка, спробуйте ще раз",
|
||||
"authenticationFailedPleaseTryAgain": "Автентифікація не пройдена. Будь ласка, спробуйте ще раз",
|
||||
"authenticationSuccessful": "Автентифікацію виконано!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Двофакторна аутентифікація успішно скинута",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Двоетапна автентифікація успішно скинута",
|
||||
"incorrectRecoveryKey": "Неправильний ключ відновлення",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "Ви ввели неправильний ключ відновлення",
|
||||
"enterPassword": "Введіть пароль",
|
||||
@@ -366,9 +370,9 @@
|
||||
"focusOnSearchBar": "Сфокусуватися на пошуку після запуску програми",
|
||||
"confirmUpdatingkey": "Ви впевнені у тому, що бажаєте змінити секретний ключ?",
|
||||
"minimizeAppOnCopy": "Згорнути програму після копіювання",
|
||||
"editCodeAuthMessage": "Аутентифікуйтесь, щоб змінити код",
|
||||
"deleteCodeAuthMessage": "Аутентифікуйтесь, щоб видалити код",
|
||||
"showQRAuthMessage": "Аутентифікуйтесь, щоб показати QR-код",
|
||||
"editCodeAuthMessage": "Авторизуйтесь, щоб змінити код",
|
||||
"deleteCodeAuthMessage": "Авторизуйтесь, щоб видалити код",
|
||||
"showQRAuthMessage": "Авторизуйтесь, щоб показати QR-код",
|
||||
"confirmAccountDeleteTitle": "Підтвердіть видалення облікового запису",
|
||||
"confirmAccountDeleteMessage": "Цей обліковий запис є зв'язаним з іншими програмами Ente, якщо ви використовуєте якісь з них.\n\nВаші завантажені дані у всіх програмах Ente будуть заплановані до видалення, а обліковий запис буде видалено назавжди.",
|
||||
"androidBiometricHint": "Підтвердити ідентифікацію",
|
||||
@@ -387,11 +391,11 @@
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Необхідна аутентифікація",
|
||||
"androidSignInTitle": "Необхідна автентифікація",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Потрібна біометрична аутентифікація",
|
||||
"androidBiometricRequiredTitle": "Потрібна біометрична автентифікація",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
@@ -407,7 +411,7 @@
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Біометрична аутентифікація не налаштована на вашому пристрої. Перейдіть в 'Налаштування > Безпека', щоб додати біометричну аутентифікацію.",
|
||||
"androidGoToSettingsDescription": "Біометрична автентифікація не налаштована на вашому пристрої. Перейдіть в «Налаштування > Безпека», щоб додати біометричну автентифікацію.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
@@ -415,7 +419,7 @@
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Біометрична аутентифікація не налаштована на вашому пристрої. Увімкніть TouchID або FaceID на вашому телефоні.",
|
||||
"iOSGoToSettingsDescription": "Біометрична автентифікація не налаштована на вашому пристрої. Увімкніть TouchID або FaceID на вашому телефоні.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Відкріпити",
|
||||
"pinnedCodeMessage": "{code} закріплено",
|
||||
"unpinnedCodeMessage": "{code} відкріплено",
|
||||
"pinned": "Закріплено",
|
||||
"tags": "Мітки",
|
||||
"createNewTag": "Створити нову мітку",
|
||||
"tag": "Мітка",
|
||||
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Tùy chỉnh",
|
||||
"editOrder": "Chỉnh sửa đơn hàng",
|
||||
"mostFrequentlyUsed": "Thường dùng",
|
||||
"mostRecentlyUsed": "Dùng gần đây",
|
||||
"activeSessions": "Các phiên làm việc hiện tại",
|
||||
"somethingWentWrongPleaseTryAgain": "Phát hiện có lỗi, xin thử lại",
|
||||
"thisWillLogYouOutOfThisDevice": "Thao tác này sẽ đăng xuất bạn khỏi thiết bị này!",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "Bỏ ghim",
|
||||
"pinnedCodeMessage": "{code} đã được ghim",
|
||||
"unpinnedCodeMessage": "{code} đã được bỏ ghim",
|
||||
"pinned": "Đã ghim",
|
||||
"tags": "Thẻ",
|
||||
"createNewTag": "Tạo thẻ mới",
|
||||
"tag": "Thẻ",
|
||||
|
||||
@@ -328,6 +328,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "自定义",
|
||||
"editOrder": "编辑顺序",
|
||||
"mostFrequentlyUsed": "经常使用",
|
||||
"mostRecentlyUsed": "最近使用",
|
||||
"activeSessions": "已登录的设备",
|
||||
"somethingWentWrongPleaseTryAgain": "出了点问题,请重试",
|
||||
"thisWillLogYouOutOfThisDevice": "这将使您登出该设备!",
|
||||
@@ -449,6 +453,7 @@
|
||||
"unpinText": "取消置顶",
|
||||
"pinnedCodeMessage": "{code} 已被置顶",
|
||||
"unpinnedCodeMessage": "{code} 已被取消置顶",
|
||||
"pinned": "已置顶",
|
||||
"tags": "标签",
|
||||
"createNewTag": "创建新标签",
|
||||
"tag": "标签",
|
||||
|
||||
15
auth/lib/models/all_icon_data.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
enum IconType { simpleIcon, customIcon }
|
||||
|
||||
class AllIconData {
|
||||
final String title;
|
||||
final IconType type;
|
||||
final String? color;
|
||||
final String? slug;
|
||||
|
||||
AllIconData({
|
||||
required this.title,
|
||||
required this.type,
|
||||
required this.color,
|
||||
this.slug,
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,8 @@ class CodeDisplay {
|
||||
String note;
|
||||
final List<String> tags;
|
||||
int position;
|
||||
String iconSrc;
|
||||
String iconID;
|
||||
|
||||
CodeDisplay({
|
||||
this.pinned = false,
|
||||
@@ -21,8 +23,12 @@ class CodeDisplay {
|
||||
this.tags = const [],
|
||||
this.note = '',
|
||||
this.position = 0,
|
||||
this.iconSrc = '',
|
||||
this.iconID = '',
|
||||
});
|
||||
|
||||
bool get isCustomIcon => (iconSrc != '' && iconID != '');
|
||||
|
||||
// copyWith
|
||||
CodeDisplay copyWith({
|
||||
bool? pinned,
|
||||
@@ -32,6 +38,8 @@ class CodeDisplay {
|
||||
List<String>? tags,
|
||||
String? note,
|
||||
int? position,
|
||||
String? iconSrc,
|
||||
String? iconID,
|
||||
}) {
|
||||
final bool updatedPinned = pinned ?? this.pinned;
|
||||
final bool updatedTrashed = trashed ?? this.trashed;
|
||||
@@ -40,6 +48,8 @@ class CodeDisplay {
|
||||
final List<String> updatedTags = tags ?? this.tags;
|
||||
final String updatedNote = note ?? this.note;
|
||||
final int updatedPosition = position ?? this.position;
|
||||
final String updatedIconSrc = iconSrc ?? this.iconSrc;
|
||||
final String updatedIconID = iconID ?? this.iconID;
|
||||
|
||||
return CodeDisplay(
|
||||
pinned: updatedPinned,
|
||||
@@ -49,6 +59,8 @@ class CodeDisplay {
|
||||
tags: updatedTags,
|
||||
note: updatedNote,
|
||||
position: updatedPosition,
|
||||
iconSrc: updatedIconSrc,
|
||||
iconID: updatedIconID,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,6 +76,8 @@ class CodeDisplay {
|
||||
tags: List<String>.from(json['tags'] ?? []),
|
||||
note: json['note'] ?? '',
|
||||
position: json['position'] ?? 0,
|
||||
iconSrc: json['iconSrc'] ?? 'ente',
|
||||
iconID: json['iconID'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -106,6 +120,8 @@ class CodeDisplay {
|
||||
'tags': tags,
|
||||
'note': note,
|
||||
'position': position,
|
||||
'iconSrc': iconSrc,
|
||||
'iconID': iconID,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/models/all_icon_data.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/models/code_display.dart';
|
||||
import 'package:ente_auth/onboarding/model/tag_enums.dart';
|
||||
@@ -13,7 +14,10 @@ import 'package:ente_auth/onboarding/view/common/tag_chip.dart';
|
||||
import 'package:ente_auth/store/code_display_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/custom_icon_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/custom_icon_page.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
@@ -42,6 +46,9 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
late List<String> selectedTags = [...?widget.code?.display.tags];
|
||||
List<String> allTags = [];
|
||||
StreamSubscription<CodesUpdatedEvent>? _streamSubscription;
|
||||
bool isCustomIcon = false;
|
||||
String _customIconID = "";
|
||||
late IconType _iconSrc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -81,6 +88,19 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
_limitTextLength(_accountController, _otherTextLimit);
|
||||
_limitTextLength(_secretController, _otherTextLimit);
|
||||
}
|
||||
|
||||
isCustomIcon = widget.code?.display.isCustomIcon ?? false;
|
||||
if (isCustomIcon) {
|
||||
_customIconID = widget.code?.display.iconID ?? "ente";
|
||||
} else {
|
||||
if (widget.code != null) {
|
||||
_customIconID = widget.code!.issuer;
|
||||
}
|
||||
}
|
||||
_iconSrc = widget.code?.display.iconSrc == "simpleIcon"
|
||||
? IconType.simpleIcon
|
||||
: IconType.customIcon;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -280,9 +300,21 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 40,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
if (widget.code != null)
|
||||
CustomIconWidget(iconData: _customIconID),
|
||||
const SizedBox(height: 24),
|
||||
if (widget.code != null)
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await navigateToCustomIconPage();
|
||||
},
|
||||
child: Text(
|
||||
"Change Icon",
|
||||
style: getEnteTextTheme(context).small,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: 400,
|
||||
child: OutlinedButton(
|
||||
@@ -324,6 +356,11 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
widget.code?.display.copyWith(tags: selectedTags) ??
|
||||
CodeDisplay(tags: selectedTags);
|
||||
display.note = notes;
|
||||
|
||||
display.iconID = _customIconID.toLowerCase();
|
||||
display.iconSrc =
|
||||
_iconSrc == IconType.simpleIcon ? 'simpleIcon' : 'customIcon';
|
||||
|
||||
if (widget.code != null && widget.code!.secret != secret) {
|
||||
ButtonResult? result = await showChoiceActionSheet(
|
||||
context,
|
||||
@@ -373,4 +410,28 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
message ?? context.l10n.pleaseVerifyDetails,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> navigateToCustomIconPage() async {
|
||||
final allIcons = IconUtils.instance.getAllIcons();
|
||||
String currentIcon;
|
||||
if (widget.code!.display.isCustomIcon) {
|
||||
currentIcon = widget.code!.display.iconID;
|
||||
} else {
|
||||
currentIcon = widget.code!.issuer;
|
||||
}
|
||||
final AllIconData newCustomIcon = await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return CustomIconPage(
|
||||
currentIcon: currentIcon,
|
||||
allIcons: allIcons,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
_customIconID = newCustomIcon.title;
|
||||
_iconSrc = newCustomIcon.type;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
56
auth/lib/services/deduplication_service.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class DeduplicationService {
|
||||
final _logger = Logger("DeduplicationService");
|
||||
|
||||
DeduplicationService._privateConstructor();
|
||||
|
||||
static final DeduplicationService instance =
|
||||
DeduplicationService._privateConstructor();
|
||||
|
||||
Future<List<DuplicateCodes>> getDuplicateCodes() async {
|
||||
try {
|
||||
final List<DuplicateCodes> result = await _getDuplicateCodes();
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to get dedupeCode", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<DuplicateCodes>> _getDuplicateCodes() async {
|
||||
final codes = await CodeStore.instance.getAllCodes();
|
||||
final List<DuplicateCodes> duplicateCodes = [];
|
||||
Map<String, List<Code>> uniqueCodes = {};
|
||||
|
||||
for (final code in codes) {
|
||||
if (code.hasError || code.isTrashed) continue;
|
||||
|
||||
final uniqueKey = "${code.secret}_${code.issuer}_${code.account}";
|
||||
|
||||
if (uniqueCodes.containsKey(uniqueKey)) {
|
||||
uniqueCodes[uniqueKey]!.add(code);
|
||||
} else {
|
||||
uniqueCodes[uniqueKey] = [code];
|
||||
}
|
||||
}
|
||||
for (final key in uniqueCodes.keys) {
|
||||
if (uniqueCodes[key]!.length > 1) {
|
||||
duplicateCodes.add(DuplicateCodes(key, uniqueCodes[key]!));
|
||||
}
|
||||
}
|
||||
return duplicateCodes;
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicateCodes {
|
||||
String hash;
|
||||
final List<Code> codes;
|
||||
|
||||
DuplicateCodes(
|
||||
this.hash,
|
||||
this.codes,
|
||||
);
|
||||
}
|
||||
@@ -445,13 +445,19 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
Widget _getIcon() {
|
||||
final String iconData;
|
||||
if (widget.code.display.isCustomIcon) {
|
||||
iconData = widget.code.display.iconID;
|
||||
} else {
|
||||
iconData = widget.code.issuer;
|
||||
}
|
||||
return Padding(
|
||||
padding: _shouldShowLargeIcon
|
||||
? EdgeInsets.only(left: widget.isCompactMode ? 12 : 16)
|
||||
: const EdgeInsets.all(0),
|
||||
child: IconUtils.instance.getIcon(
|
||||
context,
|
||||
safeDecode(widget.code.issuer).trim(),
|
||||
safeDecode(iconData).trim(),
|
||||
width: widget.isCompactMode
|
||||
? (_shouldShowLargeIcon ? 32 : 24)
|
||||
: (_shouldShowLargeIcon ? 42 : 24),
|
||||
|
||||
37
auth/lib/ui/components/custom_icon_widget.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomIconWidget extends StatelessWidget {
|
||||
final String iconData;
|
||||
|
||||
CustomIconWidget({
|
||||
super.key,
|
||||
required this.iconData,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: 70,
|
||||
height: 70,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1.5,
|
||||
color: getEnteColorScheme(context).tagChipSelectedColor,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12.0)),
|
||||
),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.contain,
|
||||
child: IconUtils.instance.getIcon(
|
||||
context,
|
||||
safeDecode(iconData).trim(),
|
||||
width: 50,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
216
auth/lib/ui/custom_icon_page.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/all_icon_data.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomIconPage extends StatefulWidget {
|
||||
final Map<String, AllIconData> allIcons;
|
||||
final String currentIcon;
|
||||
|
||||
const CustomIconPage({
|
||||
super.key,
|
||||
required this.allIcons,
|
||||
required this.currentIcon,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomIconPage> createState() => _CustomIconPageState();
|
||||
}
|
||||
|
||||
class _CustomIconPageState extends State<CustomIconPage> {
|
||||
Map<String, AllIconData> _filteredIcons = {};
|
||||
bool _showSearchBox = false;
|
||||
final bool _autoFocusSearch =
|
||||
PreferenceService.instance.shouldAutoFocusOnSearchBar();
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
String _searchText = "";
|
||||
|
||||
// Used to request focus on the search box when clicked the search icon
|
||||
late FocusNode searchBoxFocusNode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_filteredIcons = widget.allIcons;
|
||||
_showSearchBox = _autoFocusSearch;
|
||||
searchBoxFocusNode = FocusNode();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
searchBoxFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _applyFilteringAndRefresh() {
|
||||
if (_searchText.isEmpty) {
|
||||
setState(() {
|
||||
_filteredIcons = widget.allIcons;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final filteredIcons = <String, AllIconData>{};
|
||||
widget.allIcons.forEach((title, iconData) {
|
||||
if (title.toLowerCase().contains(_searchText.toLowerCase())) {
|
||||
filteredIcons[title] = iconData;
|
||||
}
|
||||
});
|
||||
|
||||
setState(() {
|
||||
_filteredIcons = filteredIcons;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: !_showSearchBox
|
||||
? const Text('Custom Branding')
|
||||
: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
autofocus: _autoFocusSearch,
|
||||
controller: _textController,
|
||||
onChanged: (value) {
|
||||
_searchText = value;
|
||||
_applyFilteringAndRefresh();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: l10n.searchHint,
|
||||
border: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
focusNode: searchBoxFocusNode,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: _showSearchBox
|
||||
? const Icon(Icons.clear)
|
||||
: const Icon(Icons.search),
|
||||
tooltip: "Search",
|
||||
onPressed: () {
|
||||
setState(
|
||||
() {
|
||||
_showSearchBox = !_showSearchBox;
|
||||
if (!_showSearchBox) {
|
||||
_textController.clear();
|
||||
_searchText = "";
|
||||
} else {
|
||||
_searchText = _textController.text;
|
||||
|
||||
// Request focus on the search box
|
||||
searchBoxFocusNode.requestFocus();
|
||||
}
|
||||
_applyFilteringAndRefresh();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 90)
|
||||
.clamp(1, double.infinity)
|
||||
.toInt(),
|
||||
crossAxisSpacing: 14,
|
||||
mainAxisSpacing: 14,
|
||||
childAspectRatio: 1,
|
||||
),
|
||||
itemCount: _filteredIcons.length,
|
||||
itemBuilder: (context, index) {
|
||||
final title = _filteredIcons.keys.elementAt(index);
|
||||
final iconData = _filteredIcons[title]!;
|
||||
IconType iconType = iconData.type;
|
||||
String? color = iconData.color;
|
||||
String? slug = iconData.slug;
|
||||
|
||||
Widget iconWidget;
|
||||
if (iconType == IconType.simpleIcon) {
|
||||
iconWidget = IconUtils.instance.getSVGIcon(
|
||||
"assets/simple-icons/icons/$title.svg",
|
||||
title,
|
||||
color,
|
||||
40,
|
||||
context,
|
||||
);
|
||||
} else {
|
||||
iconWidget = IconUtils.instance.getSVGIcon(
|
||||
"assets/custom-icons/icons/${slug ?? title}.svg",
|
||||
title,
|
||||
color,
|
||||
40,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
final newIcon = AllIconData(
|
||||
title: title,
|
||||
type: iconType,
|
||||
color: color,
|
||||
slug: slug,
|
||||
);
|
||||
Navigator.of(context).pop(newIcon);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 1.5,
|
||||
color: title.toLowerCase() ==
|
||||
widget.currentIcon.toLowerCase()
|
||||
? getEnteColorScheme(context)
|
||||
.tagChipSelectedColor
|
||||
: Colors.transparent,
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(12.0),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: iconWidget,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: title.toLowerCase() ==
|
||||
widget.currentIcon.toLowerCase()
|
||||
? const EdgeInsets.only(left: 2, right: 2)
|
||||
: const EdgeInsets.all(0.0),
|
||||
child: Text(
|
||||
'${title[0].toUpperCase()}${title.substring(1)}',
|
||||
style: getEnteTextTheme(context).mini,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -217,10 +217,11 @@ class _HomePageState extends State<HomePage> {
|
||||
void sortFilteredCodes(List<Code> codes, CodeSortKey sortKey) {
|
||||
switch (sortKey) {
|
||||
case CodeSortKey.issuerName:
|
||||
codes.sort((a, b) => a.issuer.compareTo(b.issuer));
|
||||
codes.sort((a, b) => compareAsciiLowerCaseNatural(a.issuer, b.issuer));
|
||||
break;
|
||||
case CodeSortKey.accountName:
|
||||
codes.sort((a, b) => a.account.compareTo(b.account));
|
||||
codes
|
||||
.sort((a, b) => compareAsciiLowerCaseNatural(a.account, b.account));
|
||||
break;
|
||||
case CodeSortKey.mostFrequentlyUsed:
|
||||
codes.sort((a, b) => b.display.tapCount.compareTo(a.display.tapCount));
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/code_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -16,71 +17,71 @@ class ReorderCodesPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ReorderCodesPageState extends State<ReorderCodesPage> {
|
||||
int selectedSortOption = 2;
|
||||
bool hasChanged = false;
|
||||
final logger = Logger('ReorderCodesPage');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isCompactMode = PreferenceService.instance.isCompactMode();
|
||||
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) async {
|
||||
if (!didPop) {
|
||||
final hasSaved = await saveUpadedIndexes();
|
||||
if (hasSaved) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("Custom order"),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () async {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.editOrder),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () async {
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
final hasSaved = await saveUpadedIndexes();
|
||||
if (hasSaved) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: Text(
|
||||
context.l10n.save,
|
||||
style: TextStyle(
|
||||
color: hasChanged
|
||||
? getEnteColorScheme(context).textBase
|
||||
: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: ReorderableListView(
|
||||
buildDefaultDragHandles: false,
|
||||
proxyDecorator:
|
||||
(Widget child, int index, Animation<double> animation) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (BuildContext context, _) {
|
||||
final animValue = Curves.easeInOut.transform(animation.value);
|
||||
final scale = lerpDouble(1, 1.05, animValue)!;
|
||||
return Transform.scale(scale: scale, child: child);
|
||||
},
|
||||
);
|
||||
},
|
||||
children: [
|
||||
for (final code in widget.codes)
|
||||
selectedSortOption == 2
|
||||
? ReorderableDragStartListener(
|
||||
key: ValueKey('${code.hashCode}_${code.generatedID}'),
|
||||
index: widget.codes.indexOf(code),
|
||||
child: CodeWidget(
|
||||
key: ValueKey(code.generatedID),
|
||||
code,
|
||||
isCompactMode: isCompactMode,
|
||||
),
|
||||
)
|
||||
: CodeWidget(
|
||||
key: ValueKey('${code.hashCode}_${code.generatedID}'),
|
||||
code,
|
||||
isCompactMode: isCompactMode,
|
||||
),
|
||||
],
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
if (selectedSortOption == 2) updateCodeIndex(oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ReorderableListView(
|
||||
buildDefaultDragHandles: false,
|
||||
proxyDecorator: (Widget child, int index, Animation<double> animation) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (BuildContext context, _) {
|
||||
final animValue = Curves.easeInOut.transform(animation.value);
|
||||
final scale = lerpDouble(1, 1.05, animValue)!;
|
||||
return Transform.scale(scale: scale, child: child);
|
||||
},
|
||||
);
|
||||
},
|
||||
children: [
|
||||
for (final code in widget.codes)
|
||||
ReorderableDragStartListener(
|
||||
key: ValueKey('${code.hashCode}_${code.generatedID}'),
|
||||
index: widget.codes.indexOf(code),
|
||||
child: CodeWidget(
|
||||
key: ValueKey(code.generatedID),
|
||||
code,
|
||||
isCompactMode: isCompactMode,
|
||||
),
|
||||
),
|
||||
],
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
updateCodeIndex(oldIndex, newIndex);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -97,6 +98,7 @@ class _ReorderCodesPageState extends State<ReorderCodesPage> {
|
||||
if (oldIndex < newIndex) newIndex -= 1;
|
||||
final Code code = widget.codes.removeAt(oldIndex);
|
||||
widget.codes.insert(newIndex, code);
|
||||
hasChanged = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/deduplication_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:ente_auth/ui/settings/data/duplicate_code_page.dart';
|
||||
import 'package:ente_auth/ui/settings/data/export_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/data/import_page.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -53,6 +58,33 @@ class DataSectionWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.duplicateCodes,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final List<DuplicateCodes> duplicateCodes =
|
||||
await DeduplicationService.instance.getDuplicateCodes();
|
||||
if (duplicateCodes.isEmpty) {
|
||||
unawaited(
|
||||
showErrorDialog(
|
||||
context,
|
||||
l10n.noDuplicates,
|
||||
l10n.youveNoDuplicateCodesThatCanBeCleared,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
await routeToPage(
|
||||
context,
|
||||
DuplicateCodePage(duplicateCodes: duplicateCodes),
|
||||
);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
return Column(
|
||||
children: children,
|
||||
|
||||
259
auth/lib/ui/settings/data/duplicate_code_page.dart
Normal file
@@ -0,0 +1,259 @@
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/deduplication_service.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/code_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class DuplicateCodePage extends StatefulWidget {
|
||||
final List<DuplicateCodes> duplicateCodes;
|
||||
const DuplicateCodePage({
|
||||
super.key,
|
||||
required this.duplicateCodes,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DuplicateCodePage> createState() => _DuplicateCodePageState();
|
||||
}
|
||||
|
||||
class _DuplicateCodePageState extends State<DuplicateCodePage> {
|
||||
final Logger _logger = Logger("DuplicateCodePage");
|
||||
late List<DuplicateCodes> _duplicateCodes;
|
||||
final Set<int> selectedGrids = <int>{};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_duplicateCodes = widget.duplicateCodes;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.deduplicateCodes),
|
||||
elevation: 0,
|
||||
),
|
||||
body: _getBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
final l10n = context.l10n;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
if (selectedGrids.length == _duplicateCodes.length) {
|
||||
_removeAllGrids();
|
||||
} else {
|
||||
_selectAllGrids();
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
selectedGrids.length == _duplicateCodes.length
|
||||
? l10n.deselectAll
|
||||
: l10n.selectAll,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context)
|
||||
.iconTheme
|
||||
.color!
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(left: 4)),
|
||||
selectedGrids.length == _duplicateCodes.length
|
||||
? const Icon(
|
||||
Icons.check_circle,
|
||||
size: 24,
|
||||
)
|
||||
: Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _duplicateCodes.length,
|
||||
shrinkWrap: true,
|
||||
itemBuilder: (context, index) {
|
||||
final List<Code> codes = _duplicateCodes[index].codes;
|
||||
return _getGridView(
|
||||
codes,
|
||||
index,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
selectedGrids.isEmpty ? const SizedBox.shrink() : _getDeleteButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getGridView(List<Code> code, int itemIndex) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 4,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (selectedGrids.contains(itemIndex)) {
|
||||
selectedGrids.remove(itemIndex);
|
||||
} else {
|
||||
selectedGrids.add(itemIndex);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"${code[0].issuer}, ${code.length} items",
|
||||
),
|
||||
!selectedGrids.contains(itemIndex)
|
||||
? Icon(
|
||||
Icons.check_circle_outlined,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
size: 24,
|
||||
)
|
||||
: const Icon(
|
||||
Icons.check_circle,
|
||||
size: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
AlignedGridView.count(
|
||||
crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 400)
|
||||
.clamp(1, double.infinity)
|
||||
.toInt(),
|
||||
padding: const EdgeInsets.only(bottom: 40),
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
return CodeWidget(
|
||||
key: ValueKey('${code.hashCode}_$index'),
|
||||
code[index],
|
||||
isCompactMode: false,
|
||||
);
|
||||
},
|
||||
itemCount: code.length,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getDeleteButton() {
|
||||
int selectedItemsCount = 0;
|
||||
for (int idx = 0; idx < _duplicateCodes.length; idx++) {
|
||||
if (selectedGrids.contains(idx)) {
|
||||
selectedItemsCount += _duplicateCodes[idx].codes.length - 1;
|
||||
}
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 20),
|
||||
child: SizedBox(
|
||||
width: 400,
|
||||
child: OutlinedButton(
|
||||
onPressed: () async {
|
||||
await deleteDuplicates(selectedItemsCount);
|
||||
},
|
||||
child: Text(
|
||||
"Delete $selectedItemsCount items",
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _selectAllGrids() {
|
||||
selectedGrids.clear();
|
||||
for (int idx = 0; idx < _duplicateCodes.length; idx++) {
|
||||
selectedGrids.add(idx);
|
||||
}
|
||||
}
|
||||
|
||||
void _removeAllGrids() {
|
||||
selectedGrids.clear();
|
||||
}
|
||||
|
||||
Future<void> deleteDuplicates(int itemCount) async {
|
||||
bool isAuthSuccessful =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.deleteCodeAuthMessage,
|
||||
);
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
final String message = "Are you sure you want to trash $itemCount items?";
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
title: l10n.deleteDuplicates,
|
||||
body: message,
|
||||
firstButtonLabel: l10n.trash,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
try {
|
||||
for (int idx = 0; idx < _duplicateCodes.length; idx++) {
|
||||
if (selectedGrids.contains(idx)) {
|
||||
final List<Code> codes = _duplicateCodes[idx].codes;
|
||||
for (int i = 1; i < codes.length; i++) {
|
||||
final display = codes[i].display;
|
||||
final Code code = codes[i].copyWith(
|
||||
display: display.copyWith(trashed: true),
|
||||
);
|
||||
await CodeStore.instance.addCode(code);
|
||||
}
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
_logger.severe('Failed to trash duplicate codes: ${e.toString()}');
|
||||
showGenericErrorDialog(context: context, error: e).ignore();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/export/ente.dart';
|
||||
@@ -9,16 +8,15 @@ import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/settings/data/html_export.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
Future<void> handleExportClick(BuildContext context) async {
|
||||
@@ -41,13 +39,22 @@ Future<void> handleExportClick(BuildContext context) async {
|
||||
isInAlert: true,
|
||||
buttonAction: ButtonAction.second,
|
||||
),
|
||||
const ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: "HTML",
|
||||
buttonSize: ButtonSize.large,
|
||||
isInAlert: true,
|
||||
buttonAction: ButtonAction.third,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (result?.action != null && result!.action != ButtonAction.cancel) {
|
||||
if (result.action == ButtonAction.first) {
|
||||
await _requestForEncryptionPassword(context);
|
||||
} else {
|
||||
await _showExportWarningDialog(context);
|
||||
} else if (result.action == ButtonAction.second) {
|
||||
await _showExportWarningDialog(context, "txt");
|
||||
} else if (result.action == ButtonAction.third) {
|
||||
await _showExportWarningDialog(context, "html");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +105,8 @@ Future<void> _requestForEncryptionPassword(
|
||||
),
|
||||
);
|
||||
// get json value of data
|
||||
await _exportCodes(context, jsonEncode(data.toJson()));
|
||||
} catch (e, s) {
|
||||
Logger("ExportWidget").severe(e, s);
|
||||
await _exportCodes(context, jsonEncode(data.toJson()), "txt");
|
||||
} catch (e) {
|
||||
showToast(context, "Error while exporting codes.");
|
||||
}
|
||||
}
|
||||
@@ -108,26 +114,34 @@ Future<void> _requestForEncryptionPassword(
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showExportWarningDialog(BuildContext context) async {
|
||||
Future<void> _showExportWarningDialog(BuildContext context, String type) async {
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
title: context.l10n.warning,
|
||||
body: context.l10n.exportWarningDesc,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
final data = await _getAuthDataForExport();
|
||||
await _exportCodes(context, data);
|
||||
if (type == "html") {
|
||||
final data = await generateHtml(context);
|
||||
await _exportCodes(context, data, type);
|
||||
} else {
|
||||
final data = await _getAuthDataForExport();
|
||||
await _exportCodes(context, data, type);
|
||||
}
|
||||
},
|
||||
secondButtonLabel: context.l10n.cancel,
|
||||
firstButtonLabel: context.l10n.iUnderStand,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _exportCodes(BuildContext context, String fileContent) async {
|
||||
Future<void> _exportCodes(
|
||||
BuildContext context,
|
||||
String fileContent,
|
||||
String extension,
|
||||
) async {
|
||||
DateTime now = DateTime.now().toUtc();
|
||||
String formattedDate = DateFormat('yyyy-MM-dd').format(now);
|
||||
String exportFileName = 'ente-auth-codes-$formattedDate';
|
||||
String exportFileExtension = 'txt';
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.authToExportCodes);
|
||||
await PlatformUtil.refocusWindows();
|
||||
@@ -142,14 +156,14 @@ Future<void> _exportCodes(BuildContext context, String fileContent) async {
|
||||
saveAction: () async {
|
||||
await PlatformUtil.shareFile(
|
||||
exportFileName,
|
||||
exportFileExtension,
|
||||
extension,
|
||||
CryptoUtil.strToBin(fileContent),
|
||||
MimeType.text,
|
||||
);
|
||||
},
|
||||
sendAction: () async {
|
||||
final codeFile = File(
|
||||
"${Configuration.instance.getTempDirectory()}$exportFileName.$exportFileExtension",
|
||||
"${Configuration.instance.getTempDirectory()}$exportFileName.$extension",
|
||||
);
|
||||
if (codeFile.existsSync()) {
|
||||
await codeFile.delete();
|
||||
|
||||
236
auth/lib/ui/settings/data/html_export.dart
Normal file
@@ -0,0 +1,236 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
Future<String> generateQRImageBase64(String data) async {
|
||||
final qrPainter = QrPainter(
|
||||
data: data,
|
||||
version: QrVersions.auto,
|
||||
eyeStyle: const QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Colors.black,
|
||||
),
|
||||
dataModuleStyle: const QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Colors.black,
|
||||
),
|
||||
);
|
||||
|
||||
const size = 250.0;
|
||||
final recorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(recorder);
|
||||
qrPainter.paint(canvas, const Size(size, size));
|
||||
final picture = recorder.endRecording();
|
||||
final img = await picture.toImage(size.toInt(), size.toInt());
|
||||
final byteData = await img.toByteData(format: ui.ImageByteFormat.png);
|
||||
final pngBytes = byteData!.buffer.asUint8List();
|
||||
|
||||
return base64Encode(pngBytes);
|
||||
}
|
||||
|
||||
Future<String> generateOTPEntryHtml(
|
||||
Code code,
|
||||
BuildContext context,
|
||||
) async {
|
||||
final qrBase64 = await generateQRImageBase64(code.rawData);
|
||||
String notes = code.display.note;
|
||||
if (notes.isNotEmpty) {
|
||||
notes = '<p>Note: <b>$notes</b></p>';
|
||||
}
|
||||
return '''
|
||||
<div class="otp-entry">
|
||||
<div>
|
||||
<p><b>${code.issuer}</b> </p>
|
||||
<p><b>${code.account}</b></p>
|
||||
<br />
|
||||
<p>Type: <b>${code.type.name}</b></p>
|
||||
<p>Algorithm: <b>${code.algorithm.name}</b></p>
|
||||
<p>Digits: <b>${code.digits}</b></p>
|
||||
<p>Recovery Code: <b>${code.secret}</b></p>
|
||||
$notes
|
||||
</div>
|
||||
<img src="data:image/png;base64,$qrBase64" alt="QR Code">
|
||||
</div>
|
||||
<br/>
|
||||
<hr class="red-separator" />
|
||||
<br/>
|
||||
''';
|
||||
}
|
||||
|
||||
Future<String> generateHtml(BuildContext context) async {
|
||||
DateTime now = DateTime.now().toUtc();
|
||||
String formattedDate = DateFormat('d MMMM, yyyy').format(now);
|
||||
final allCodes = await CodeStore.instance.getAllCodes();
|
||||
final List<String> enteries = [];
|
||||
|
||||
for (final code in allCodes) {
|
||||
if (code.hasError) continue;
|
||||
final entry = await generateOTPEntryHtml(code, context);
|
||||
enteries.add(entry);
|
||||
}
|
||||
|
||||
return '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
background-color: #f0f1f3;
|
||||
font-family: "Helvetica Neue", "Segoe UI", Helvetica, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 27px;
|
||||
margin: 0;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4f4;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-color: #ddd;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
background-color: #fff;
|
||||
padding: 30px;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background: #0055d4;
|
||||
border-radius: 3px;
|
||||
text-decoration: none !important;
|
||||
color: #fff !important;
|
||||
font-weight: bold;
|
||||
padding: 10px 30px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: #111;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #888;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0055d4;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.wrap {
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-icons {
|
||||
padding: 4px !important;
|
||||
width: 24px !important;
|
||||
}
|
||||
|
||||
.otp-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.otp-entry div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.otp-entry p {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
hr.red-separator {
|
||||
border: none;
|
||||
height: 1px;
|
||||
background-color: rgb(173, 0, 255);
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 style="text-align: center;">Ente Auth</h1>
|
||||
<h4 style="text-align: center; margin-bottom: 5px;">OTP Data Export</h4>
|
||||
<p style="text-align: center; margin-top: 0px;">$formattedDate</p>
|
||||
<div class="gutter" style="padding: 4px"> </div>
|
||||
<div class="wrap" style=" background-color: rgb(255, 255, 255); padding: 2px
|
||||
30px 30px 30px; max-width: 700px; margin: 0 auto; border-radius: 5px;
|
||||
font-size: 16px; ">
|
||||
<main>
|
||||
<p>
|
||||
${enteries.join('')}
|
||||
</p>
|
||||
</main>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="footer" style="text-align: center; font-size: 12px; color:
|
||||
rgb(136, 136, 136)">
|
||||
<div>
|
||||
<a href="https://ente.io" target="_blank"><img src="https://email-assets.ente.io/ente-green.png" style="width: 100px;
|
||||
padding: 24px" title="Ente" alt="Ente" /></a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://fosstodon.org/@ente" target="_blank"><img src="https://email-assets.ente.io/mastodon-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="Mastodon" alt="Mastodon" /></a>
|
||||
<a href="https://twitter.com/enteio" target="_blank"><img src="https://email-assets.ente.io/twitter-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="Twitter" alt="Twitter" /></a>
|
||||
<a href="https://discord.ente.io" target="_blank"><img src="https://email-assets.ente.io/discord-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="Discord" alt="Discord" /></a>
|
||||
<a href="https://github.com/ente-io" target="_blank"><img src="https://email-assets.ente.io/github-icon.png"
|
||||
class="footer-icons" style="width: 24px; padding: 4px" title="GitHub" alt="GitHub" /></a>
|
||||
</div>
|
||||
<p>
|
||||
Ente Technologies, Inc.
|
||||
<br /> 1111B S Governors Ave 6032 Dover, DE 19904
|
||||
</p>
|
||||
<br />
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
''';
|
||||
}
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
|
||||
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_widget.dart';
|
||||
import 'package:ente_auth/ui/settings/common_settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class ThemeSwitchWidget extends StatefulWidget {
|
||||
const ThemeSwitchWidget({super.key});
|
||||
@@ -42,7 +40,7 @@ class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: "Theme",
|
||||
title: context.l10n.theme,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Theme.of(context).brightness == Brightness.light
|
||||
? Icons.light_mode_outlined
|
||||
@@ -64,10 +62,21 @@ class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
|
||||
);
|
||||
}
|
||||
|
||||
String _name(BuildContext ctx, AdaptiveThemeMode mode) {
|
||||
switch (mode) {
|
||||
case AdaptiveThemeMode.light:
|
||||
return ctx.l10n.lightTheme;
|
||||
case AdaptiveThemeMode.dark:
|
||||
return ctx.l10n.darkTheme;
|
||||
case AdaptiveThemeMode.system:
|
||||
return ctx.l10n.systemTheme;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: toBeginningOfSentenceCase(themeMode.name)!,
|
||||
title: _name(context, themeMode),
|
||||
textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/models/all_icon_data.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -24,6 +25,80 @@ class IconUtils {
|
||||
await _loadJson();
|
||||
}
|
||||
|
||||
Map<String, AllIconData> getAllIcons() {
|
||||
Set<String> processedIconPaths = {};
|
||||
final allIcons = <String, AllIconData>{};
|
||||
|
||||
final simpleIterator = _simpleIcons.entries.iterator;
|
||||
final customIterator = _customIcons.entries.iterator;
|
||||
|
||||
var simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null;
|
||||
var customEntry = customIterator.moveNext() ? customIterator.current : null;
|
||||
|
||||
String simpleIconPath, customIconPath;
|
||||
|
||||
while (simpleEntry != null && customEntry != null) {
|
||||
if (simpleEntry.key.compareTo(customEntry.key) <= 0) {
|
||||
simpleIconPath = "assets/simple-icons/icons/${simpleEntry.key}.svg";
|
||||
if (!processedIconPaths.contains(simpleIconPath)) {
|
||||
allIcons[simpleEntry.key] = AllIconData(
|
||||
title: simpleEntry.key,
|
||||
type: IconType.simpleIcon,
|
||||
color: simpleEntry.value,
|
||||
);
|
||||
processedIconPaths.add(simpleIconPath);
|
||||
}
|
||||
simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null;
|
||||
} else {
|
||||
customIconPath =
|
||||
"assets/custom-icons/icons/${customEntry.value.slug ?? customEntry.key}.svg";
|
||||
|
||||
if (!processedIconPaths.contains(customIconPath)) {
|
||||
allIcons[customEntry.key] = AllIconData(
|
||||
title: customEntry.key,
|
||||
type: IconType.customIcon,
|
||||
color: customEntry.value.color,
|
||||
slug: customEntry.value.slug,
|
||||
);
|
||||
processedIconPaths.add(customIconPath);
|
||||
}
|
||||
customEntry = customIterator.moveNext() ? customIterator.current : null;
|
||||
}
|
||||
}
|
||||
|
||||
while (simpleEntry != null) {
|
||||
simpleIconPath = "assets/simple-icons/icons/${simpleEntry.key}.svg";
|
||||
|
||||
if (!processedIconPaths.contains(simpleIconPath)) {
|
||||
allIcons[simpleEntry.key] = AllIconData(
|
||||
title: simpleEntry.key,
|
||||
type: IconType.simpleIcon,
|
||||
color: simpleEntry.value,
|
||||
);
|
||||
processedIconPaths.add(simpleIconPath);
|
||||
}
|
||||
simpleEntry = simpleIterator.moveNext() ? simpleIterator.current : null;
|
||||
}
|
||||
|
||||
while (customEntry != null) {
|
||||
customIconPath =
|
||||
"assets/custom-icons/icons/${customEntry.value.slug ?? customEntry.key}.svg";
|
||||
|
||||
if (!processedIconPaths.contains(customIconPath)) {
|
||||
allIcons[customEntry.key] = AllIconData(
|
||||
title: customEntry.key,
|
||||
type: IconType.customIcon,
|
||||
color: customEntry.value.color,
|
||||
slug: customEntry.value.slug,
|
||||
);
|
||||
processedIconPaths.add(customIconPath);
|
||||
}
|
||||
customEntry = customIterator.moveNext() ? customIterator.current : null;
|
||||
}
|
||||
|
||||
return allIcons;
|
||||
}
|
||||
|
||||
Widget getIcon(
|
||||
BuildContext context,
|
||||
String provider, {
|
||||
@@ -38,7 +113,7 @@ class IconUtils {
|
||||
);
|
||||
for (final title in titlesList) {
|
||||
if (_customIcons.containsKey(title)) {
|
||||
return _getSVGIcon(
|
||||
return getSVGIcon(
|
||||
"assets/custom-icons/icons/${_customIcons[title]!.slug ?? title}.svg",
|
||||
title,
|
||||
_customIcons[title]!.color,
|
||||
@@ -46,7 +121,7 @@ class IconUtils {
|
||||
context,
|
||||
);
|
||||
} else if (_simpleIcons.containsKey(title)) {
|
||||
return _getSVGIcon(
|
||||
return getSVGIcon(
|
||||
"assets/simple-icons/icons/$title.svg",
|
||||
title,
|
||||
_simpleIcons[title],
|
||||
@@ -75,7 +150,7 @@ class IconUtils {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _getSVGIcon(
|
||||
Widget getSVGIcon(
|
||||
String path,
|
||||
String title,
|
||||
String? color,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 4.1.5+415
|
||||
version: 4.1.7+417
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
|
||||
@@ -72,7 +72,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Point origin(70, 70);
|
||||
Win32Window::Size size(1280, 720);
|
||||
if (!window.Create(L"Ente Auth", origin, size))
|
||||
{
|
||||
|
||||
@@ -11,7 +11,14 @@
|
||||
|
||||
import { nativeImage, shell } from "electron/common";
|
||||
import type { WebContents } from "electron/main";
|
||||
import { BrowserWindow, Menu, Tray, app, protocol } from "electron/main";
|
||||
import {
|
||||
BrowserWindow,
|
||||
Menu,
|
||||
Tray,
|
||||
app,
|
||||
dialog,
|
||||
protocol,
|
||||
} from "electron/main";
|
||||
import serveNextAt from "next-electron-server";
|
||||
import { existsSync } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
@@ -131,6 +138,7 @@ const main = () => {
|
||||
const webContents = mainWindow.webContents;
|
||||
setDownloadPath(webContents);
|
||||
allowExternalLinks(webContents);
|
||||
handleBackOnStripeCheckout(mainWindow);
|
||||
allowAllCORSOrigins(webContents);
|
||||
|
||||
// Start loading the renderer.
|
||||
@@ -502,6 +510,45 @@ const allowExternalLinks = (webContents: WebContents) =>
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle back button presses on the Stripe checkout page.
|
||||
*
|
||||
* For payments, we show the Stripe checkout page to the user in the app's
|
||||
* window. On this page there is a back button that allows the user to get back
|
||||
* to the app's contents. Since we're not showing the browser controls, this is
|
||||
* the only way to get back to the app.
|
||||
*
|
||||
* If the user enters something in the text fields on this page (e.g. if they
|
||||
* start entering their credit card number), and then press back, then the
|
||||
* browser shows the user a dialog asking them to confirm if they want to
|
||||
* discard their unsaved changes. However, when running in the context of an
|
||||
* Electron app, this dialog is not shown, and instead the app just gets stuck
|
||||
* (the back button stops working, and quitting the app also doesn't work since
|
||||
* there is an invisible modal dialog).
|
||||
*
|
||||
* So we instead intercept these back button presses, and show the same dialog
|
||||
* that the browser would've shown.
|
||||
*/
|
||||
const handleBackOnStripeCheckout = (window: BrowserWindow) =>
|
||||
window.webContents.on("will-prevent-unload", (event) => {
|
||||
const url = new URL(window.webContents.getURL());
|
||||
// Only intercept on Stripe checkout pages.
|
||||
if (url.host != "checkout.stripe.com") return;
|
||||
|
||||
// The dialog copy is similar to what Chrome would've shown.
|
||||
// https://www.electronjs.org/docs/latest/api/web-contents#event-will-prevent-unload
|
||||
const choice = dialog.showMessageBoxSync(window, {
|
||||
type: "question",
|
||||
buttons: ["Leave", "Stay"],
|
||||
title: "Leave site?",
|
||||
message: "Changes that you made may not be saved.",
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
});
|
||||
const leave = choice === 0;
|
||||
if (leave) event.preventDefault();
|
||||
});
|
||||
|
||||
/**
|
||||
* Allow uploads to arbitrary S3 buckets.
|
||||
*
|
||||
|
||||
@@ -39,6 +39,10 @@ export const sidebar = [
|
||||
link: "/photos/features/free-up-space/",
|
||||
},
|
||||
{ text: "Hidden photos", link: "/photos/features/hide" },
|
||||
{
|
||||
text: "Legacy",
|
||||
link: "/photos/features/legacy/",
|
||||
},
|
||||
{
|
||||
text: "Location tags",
|
||||
link: "/photos/features/location-tags",
|
||||
|
||||
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/docs/photos/features/legacy/add_trusted_contact.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
50
docs/docs/photos/features/legacy/index.md
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: Legacy
|
||||
description: Using Legacy to pass on your memories to loved ones
|
||||
---
|
||||
|
||||
# Legacy
|
||||
|
||||
Legacy allows trusted contacts to recover your account in your absence. The main usecase here is to pass on your memories after your death. It can also be useful for other cases - for e.g., when you forget your password and recovery key.
|
||||
|
||||
Trusted Contacts can initiate a recovery, and if not blocked in 30 days, would be able to change the password to your account and thereby access your memories.
|
||||
|
||||
## Adding a trusted contact
|
||||
|
||||
You can add a trusted contact for your account using the mobile app for Ente Photos. Go to Settings -> Account -> Legacy, and click on "Add Trusted Contact".
|
||||
|
||||
You would be asked to enter the email address of the trusted contact you want to add or choose from a list of contacts on Ente. Please note that the trusted contact must be an Ente user.
|
||||
|
||||
<div align="center">
|
||||
|
||||
{width=300px}
|
||||
|
||||
</div>
|
||||
|
||||
The trusted contact must accept your request. They can do so by going to Settings -> Account -> Legacy in the Ente Photos mobile app, and clicking on your email address within the Legacy accounts sections. Ente would also send an email notification to the trusted contact to nudge them to accept the invite.
|
||||
|
||||
<div align="center">
|
||||
|
||||
{width=300px}
|
||||
|
||||
</div>
|
||||
|
||||
## Recovering an account as a trusted contact
|
||||
|
||||
As a trusted contact, you can recover an account by going to Settings -> Account -> Legacy in the Ente photos app, tapping on the email address of the account within the Legacy account sections.
|
||||
|
||||
<div align="center">
|
||||
|
||||
{width=300px}
|
||||
|
||||
</div>
|
||||
|
||||
Once the recovery is initiated, the account owner would get 30 days to block the recovery. After 30 days, you can go the same page in the app, where you will be prompted to change the password of the account. Once you change the password, you would be able to access the recovered account with the new password.
|
||||
|
||||
## Blocking account recovery by a trusted contact
|
||||
|
||||
After a trusted contact initiates a recover, you, as the account owner, would get 30 days to block the recovery. To do this, you must go to Settings -> Account -> Legacy, where you will see a message that recovery of the account has been initiated. Tapping on that will allow you to block the recovery.
|
||||
|
||||
## Removing a trusted contact
|
||||
|
||||
You can remove a trusted contact by going to Settings -> Account -> Legacy, tapping on the trusted contact you want to remove, and choosing "Remove" in the popup.
|
||||
BIN
docs/docs/photos/features/legacy/initiate_account_recovery.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -39,6 +39,10 @@ device.
|
||||
> desktop app first, because it can index your existing photos faster. Once your
|
||||
> existing photos have been indexed, then you can use either. The mobile app is
|
||||
> fast enough to index new photos as they are being backed up.
|
||||
>
|
||||
> Also, it is beneficial to enable machine learning before importing your
|
||||
> photos, as this allows the Ente app to index your files as they are getting
|
||||
> uploaded instead of needing to download them again.
|
||||
|
||||
The indexes are synced across all your devices automatically using the same
|
||||
end-to-end encrypted security that we use for syncing your photos.
|
||||
|
||||
@@ -68,3 +68,10 @@ will ignore already backed up files and upload just the rest.
|
||||
|
||||
If you run into any issues during this migration, please reach out to
|
||||
[support@ente.io](mailto:support@ente.io) and we will be happy to help you!
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> In case you wish to use face recognition and other advanced search features
|
||||
> provided by Ente, we recommend that you enable [machine
|
||||
> learning](/photos/features/machine-learning) before importing your photos so
|
||||
> that the Ente app can directly index files as they are getting uploaded.
|
||||
|
||||
20
mobile/assets/icons/legacy-dark.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34053_111310)">
|
||||
<path d="M170 66.7593C170 71.8084 169.042 76.732 167.151 81.3933C165.192 86.2233 162.316 90.5536 158.606 94.265L147.36 105.511L151.657 109.806C152.962 111.112 152.26 113.347 150.441 113.671L128.349 117.598C126.785 117.875 125.423 116.513 125.7 114.949L129.628 92.8595C129.952 91.0413 132.187 90.3393 133.492 91.6442L137.12 95.2717L148.366 84.0261C152.978 79.4144 155.519 73.282 155.519 66.7609C155.519 60.2383 152.978 54.105 148.365 49.4941C143.753 44.8815 137.621 42.3422 131.097 42.3422C124.574 42.3422 118.442 44.8815 113.83 49.4941L107.387 55.936C107.383 55.9064 107.378 55.8784 107.373 55.8488L104.299 38.5628C107.848 35.1847 111.936 32.5447 116.463 30.7097C121.123 28.818 126.048 27.8594 131.097 27.8594C136.146 27.8594 141.07 28.818 145.732 30.7089C150.564 32.667 154.894 35.5421 158.606 39.2528C162.317 42.9643 165.192 47.2946 167.151 52.1246C169.041 56.7859 169.999 61.711 170 66.7593Z" fill="#4AAF3C"/>
|
||||
<path d="M133.814 119.087L105.12 147.779C103.762 149.137 101.92 149.9 99.9998 149.9C98.0791 149.9 96.2375 149.137 94.879 147.779L70.7775 123.678L67.503 126.953C66.1972 128.259 63.9623 127.556 63.6392 125.737L59.7107 103.646C59.4332 102.083 60.7966 100.72 62.3598 100.997L84.4519 104.925C86.2702 105.249 86.9731 107.483 85.6673 108.789L81.0183 113.438L100.001 132.42L124.507 107.914L123.318 114.601C123.053 116.096 123.535 117.63 124.609 118.703C125.684 119.778 127.217 120.259 128.712 119.994L133.814 119.087H133.814Z" fill="#4AAF3C"/>
|
||||
<path d="M102.359 58.8607L80.2668 54.9325C78.4485 54.6095 77.7456 52.3748 79.0514 51.0692L83.1799 46.9412C79.0498 43.9533 74.1009 42.3406 68.9034 42.3406C62.3808 42.3406 56.2477 44.88 51.6363 49.4925C47.0232 54.1042 44.4828 60.2359 44.4828 66.7585C44.4828 73.2812 47.0232 79.4128 51.6363 84.0246L66.9435 99.3301L62.7415 98.5834C61.2462 98.3179 59.7125 98.8 58.6386 99.8738C57.5647 100.948 57.0825 102.482 57.348 103.977L58.697 111.564L41.3955 94.2635C37.6836 90.552 34.8089 86.2217 32.8499 81.3917C30.9588 76.7312 30 71.8076 30 66.7593C30 61.711 30.9588 56.7867 32.8491 52.1254C34.8081 47.2946 37.6836 42.9643 41.3947 39.2536C45.1057 35.5421 49.4365 32.6678 54.267 30.7097C58.9296 28.818 63.8537 27.8594 68.9034 27.8594C73.953 27.8594 78.8771 28.818 83.5389 30.7081C87.158 32.1753 90.4956 34.1565 93.503 36.6183L97.2157 32.9061C98.5215 31.6004 100.756 32.3032 101.079 34.1214L105.007 56.211C105.286 57.7741 103.922 59.1381 102.359 58.8607Z" fill="#4AAF3C"/>
|
||||
</g>
|
||||
<g filter="url(#filter0_f_34053_111310)">
|
||||
<ellipse cx="102.576" cy="170.536" rx="27.5758" ry="1.59085" fill="#1CA609" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_34053_111310" x="70.5" y="164.445" width="64.1515" height="12.1797" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="2.25" result="effect1_foregroundBlur_34053_111310"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_34053_111310">
|
||||
<rect width="140" height="122" fill="white" transform="translate(30 27.8828)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
20
mobile/assets/icons/legacy-light.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34053_111286)">
|
||||
<path d="M170 66.7593C170 71.8084 169.042 76.732 167.151 81.3933C165.192 86.2233 162.316 90.5536 158.606 94.265L147.36 105.511L151.657 109.806C152.962 111.112 152.26 113.347 150.441 113.671L128.349 117.598C126.785 117.875 125.423 116.513 125.7 114.949L129.628 92.8594C129.952 91.0413 132.187 90.3393 133.492 91.6441L137.12 95.2717L148.366 84.0261C152.978 79.4144 155.519 73.2819 155.519 66.7609C155.519 60.2383 152.978 54.105 148.365 49.4941C143.753 44.8815 137.621 42.3422 131.097 42.3422C124.574 42.3422 118.442 44.8815 113.83 49.4941L107.387 55.936C107.383 55.9064 107.378 55.8784 107.373 55.8488L104.299 38.5628C107.848 35.1847 111.936 32.5447 116.463 30.7097C121.123 28.818 126.048 27.8594 131.097 27.8594C136.146 27.8594 141.07 28.818 145.732 30.7089C150.564 32.667 154.894 35.5421 158.606 39.2528C162.317 42.9643 165.192 47.2946 167.151 52.1246C169.041 56.7859 169.999 61.7102 170 66.7585V66.7593Z" fill="#4AAF3C"/>
|
||||
<path d="M133.814 119.087L105.12 147.779C103.762 149.137 101.92 149.9 99.9998 149.9C98.0791 149.9 96.2375 149.137 94.879 147.779L70.7775 123.678L67.503 126.953C66.1972 128.259 63.9623 127.556 63.6392 125.737L59.7107 103.646C59.4332 102.083 60.7966 100.72 62.3598 100.997L84.4519 104.925C86.2702 105.249 86.9731 107.483 85.6673 108.789L81.0183 113.438L100.001 132.42L124.507 107.914L123.318 114.601C123.053 116.096 123.535 117.63 124.609 118.703C125.684 119.778 127.217 120.259 128.712 119.994L133.814 119.087H133.814Z" fill="#4AAF3C"/>
|
||||
<path d="M102.359 58.8607L80.2668 54.9325C78.4485 54.6095 77.7456 52.3748 79.0514 51.0692L83.1799 46.9412C79.0498 43.9533 74.1009 42.3406 68.9034 42.3406C62.3808 42.3406 56.2477 44.88 51.6363 49.4925C47.0232 54.1042 44.4828 60.2359 44.4828 66.7585C44.4828 73.2812 47.0232 79.4128 51.6363 84.0246L66.9435 99.3301L62.7415 98.5834C61.2462 98.3179 59.7125 98.8 58.6386 99.8738C57.5647 100.948 57.0825 102.482 57.348 103.977L58.697 111.564L41.3955 94.2635C37.6836 90.552 34.8089 86.2217 32.8499 81.3917C30.9588 76.7312 30 71.8076 30 66.7593C30 61.711 30.9588 56.7867 32.8491 52.1254C34.8081 47.2946 37.6836 42.9643 41.3947 39.2536C45.1057 35.5421 49.4365 32.6678 54.267 30.7097C58.9296 28.818 63.8537 27.8594 68.9034 27.8594C73.953 27.8594 78.8771 28.818 83.5389 30.7081C87.158 32.1753 90.4957 34.1565 93.503 36.6183L97.2157 32.9061C98.5215 31.6004 100.756 32.3032 101.079 34.1214L105.007 56.211C105.286 57.7741 103.922 59.1373 102.359 58.8599V58.8607Z" fill="#4AAF3C"/>
|
||||
</g>
|
||||
<g filter="url(#filter0_f_34053_111286)">
|
||||
<ellipse cx="101.576" cy="170.536" rx="27.5758" ry="1.59085" fill="#E1E1E1" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_34053_111286" x="69.5" y="164.445" width="64.1515" height="12.1797" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="2.25" result="effect1_foregroundBlur_34053_111286"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_34053_111286">
|
||||
<rect width="140" height="122" fill="white" transform="translate(30 27.8828)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
3
mobile/devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@@ -0,0 +1 @@
|
||||
എന്റേ - പൂർണമായും എൻക്രിപ്റ്റ് ചെയ്ത ചിത്രസംഭരണി
|
||||
1
mobile/fastlane/metadata/android/ml/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
എന്റേ - എൻക്രിപ്റ്റ്ട് ചിത്രസംഭരണി
|
||||
@@ -1 +1 @@
|
||||
Fotografie,Familie,Privatsphäre,Cloud,Backup,Video,Foto,Verschlüsselung,Speicher,Album,Alternative
|
||||
Fotos,Fotografie,Familie,Privatsphäre,Cloud,Backup,Videos,Foto,Verschlüsselung,Speicher,Album,Alternative
|
||||
|
||||
1
mobile/fastlane/metadata/ios/ml/keywords.txt
Normal file
@@ -0,0 +1 @@
|
||||
ഫോട്ടോകൾ, ചിത്രങ്ങൾ, പടങ്ങൾ, ഫോട്ടോഗ്രാഫി, കുടുംബം, ഛായാഗ്രഹണം, സ്വകാര്യത, ക്ലൌഡ്, ബാക്കപ്പ്, വീഡിയോകൾ, ഫോട്ടോ, ചിത്രം, പടം, എൻക്രിപ്ഷൻ, സ്റ്റോറേജ്, ആൽബം, ബദൽ
|
||||
1
mobile/fastlane/metadata/ios/ml/name.txt
Normal file
@@ -0,0 +1 @@
|
||||
എന്റേ ചിത്രസംഭരണി
|
||||
1
mobile/fastlane/metadata/ios/ml/subtitle.txt
Normal file
@@ -0,0 +1 @@
|
||||
എൻക്രിപ്റ്റ്ട് ചിത്രസംഭരണം
|
||||
1
mobile/fastlane/metadata/playstore/ml/title.txt
Normal file
@@ -0,0 +1 @@
|
||||
എന്റേ ചിത്രസംഭരണി
|
||||
@@ -17,6 +17,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:photos/core/error-reporting/tunneled_transport.dart';
|
||||
import "package:photos/core/errors.dart";
|
||||
import 'package:photos/models/typedefs.dart';
|
||||
import "package:photos/utils/device_info.dart";
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -198,6 +199,12 @@ class SuperLogging {
|
||||
$.info("sentry uploader started");
|
||||
}
|
||||
|
||||
unawaited(
|
||||
getDeviceName().then((name) {
|
||||
$.info("Device name: $name");
|
||||
}),
|
||||
);
|
||||
|
||||
if (appConfig.body == null) return;
|
||||
|
||||
if (enable && sentryIsEnabled) {
|
||||
|
||||
557
mobile/lib/emergency/emergency_page.dart
Normal file
@@ -0,0 +1,557 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_svg/flutter_svg.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/emergency/emergency_service.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/emergency/other_contact_page.dart";
|
||||
import "package:photos/emergency/select_contact_page.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import "package:photos/ui/common/loading_widget.dart";
|
||||
import "package:photos/ui/components/action_sheet_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/divider_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_section_title.dart';
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/notification_widget.dart";
|
||||
import 'package:photos/ui/components/title_bar_title_widget.dart';
|
||||
import 'package:photos/ui/components/title_bar_widget.dart';
|
||||
import "package:photos/ui/sharing/user_avator_widget.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
class EmergencyPage extends StatefulWidget {
|
||||
const EmergencyPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EmergencyPage> createState() => _EmergencyPageState();
|
||||
}
|
||||
|
||||
class _EmergencyPageState extends State<EmergencyPage> {
|
||||
late int currentUserID;
|
||||
EmergencyInfo? info;
|
||||
|
||||
bool hasTrustedContact = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
currentUserID = Configuration.instance.getUserID()!;
|
||||
// set info to null after 5 second
|
||||
Future.delayed(
|
||||
const Duration(seconds: 0),
|
||||
() async {
|
||||
unawaited(_fetchData());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchData() async {
|
||||
try {
|
||||
final result = await EmergencyContactService.instance.getInfo();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
info = result;
|
||||
if (info != null) {
|
||||
hasTrustedContact = info!.contacts.isNotEmpty;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
showShortToast(
|
||||
context,
|
||||
S.of(context).somethingWentWrong,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final currentUserID = Configuration.instance.getUserID()!;
|
||||
final List<EmergencyContact> othersTrustedContacts =
|
||||
info?.othersEmergencyContact ?? [];
|
||||
final List<EmergencyContact> trustedContacts = info?.contacts ?? [];
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: false,
|
||||
slivers: <Widget>[
|
||||
TitleBarWidget(
|
||||
flexibleSpaceTitle: TitleBarTitleWidget(
|
||||
title: S.of(context).legacy,
|
||||
),
|
||||
),
|
||||
if (info == null)
|
||||
const SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Center(
|
||||
child: EnteLoadingWidget(),
|
||||
),
|
||||
),
|
||||
if (info != null)
|
||||
if (info!.recoverSessions.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == 0) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: NotificationWidget(
|
||||
startIcon: Icons.warning_amber_rounded,
|
||||
text: context.l10n.recoveryWarning,
|
||||
actionIcon: null,
|
||||
onTap: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
final RecoverySessions recoverSession =
|
||||
info!.recoverSessions[index - 1];
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: recoverSession.emergencyContact.email,
|
||||
makeTextBold: recoverSession.status.isNotEmpty,
|
||||
textColor: colorScheme.warning500,
|
||||
),
|
||||
leadingIconWidget: UserAvatarWidget(
|
||||
recoverSession.emergencyContact,
|
||||
currentUserID: currentUserID,
|
||||
),
|
||||
leadingIconSize: 24,
|
||||
menuItemColor: colorScheme.fillFaint,
|
||||
singleBorderRadius: 8,
|
||||
trailingIcon: Icons.chevron_right,
|
||||
onTap: () async {
|
||||
await showRejectRecoveryDialog(recoverSession);
|
||||
},
|
||||
);
|
||||
},
|
||||
childCount: 1 + info!.recoverSessions.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (info != null)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 8,
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == 0 && trustedContacts.isNotEmpty) {
|
||||
return MenuSectionTitle(
|
||||
title: S.of(context).trustedContacts,
|
||||
);
|
||||
} else if (index > 0 && index <= trustedContacts.length) {
|
||||
final listIndex = index - 1;
|
||||
final contact = trustedContacts[listIndex];
|
||||
return Column(
|
||||
children: [
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: contact.emergencyContact.email,
|
||||
subTitle: contact.isPendingInvite() ? "⚠" : null,
|
||||
makeTextBold: contact.isPendingInvite(),
|
||||
),
|
||||
leadingIconSize: 24.0,
|
||||
surfaceExecutionStates: false,
|
||||
alwaysShowSuccessState: false,
|
||||
leadingIconWidget: UserAvatarWidget(
|
||||
contact.emergencyContact,
|
||||
type: AvatarType.mini,
|
||||
currentUserID: currentUserID,
|
||||
),
|
||||
menuItemColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await showRevokeOrRemoveDialog(context, contact);
|
||||
},
|
||||
isTopBorderRadiusRemoved: listIndex > 0,
|
||||
isBottomBorderRadiusRemoved: true,
|
||||
singleBorderRadius: 8,
|
||||
),
|
||||
DividerWidget(
|
||||
dividerType: DividerType.menu,
|
||||
bgColor: getEnteColorScheme(context).fillFaint,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (index == (1 + trustedContacts.length)) {
|
||||
if (trustedContacts.isEmpty) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
context.l10n.legacyPageDesc,
|
||||
style: getEnteTextTheme(context).body,
|
||||
),
|
||||
SizedBox(
|
||||
height: 200,
|
||||
width: 200,
|
||||
child: SvgPicture.asset(
|
||||
getEnteColorScheme(context).backdropBase ==
|
||||
backgroundBaseDark
|
||||
? "assets/icons/legacy-light.svg"
|
||||
: "assets/icons/legacy-dark.svg",
|
||||
width: 156,
|
||||
height: 152,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
context.l10n.legacyPageDesc2,
|
||||
style: getEnteTextTheme(context).smallMuted,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: S.of(context).addTrustedContact,
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
await routeToPage(
|
||||
context,
|
||||
AddContactPage(info!),
|
||||
forceCustomPageRoute: true,
|
||||
);
|
||||
unawaited(_fetchData());
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: trustedContacts.isNotEmpty
|
||||
? S.of(context).addMore
|
||||
: S.of(context).addTrustedContact,
|
||||
makeTextBold: true,
|
||||
),
|
||||
leadingIcon: Icons.add_outlined,
|
||||
surfaceExecutionStates: false,
|
||||
menuItemColor: getEnteColorScheme(context).fillFaint,
|
||||
onTap: () async {
|
||||
await routeToPage(
|
||||
context,
|
||||
AddContactPage(info!),
|
||||
forceCustomPageRoute: true,
|
||||
);
|
||||
unawaited(_fetchData());
|
||||
},
|
||||
isTopBorderRadiusRemoved: trustedContacts.isNotEmpty,
|
||||
singleBorderRadius: 8,
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
childCount: 1 + trustedContacts.length + 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (info != null && info!.othersEmergencyContact.isNotEmpty)
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.only(top: 0, left: 16, right: 16),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if (index == 0 && (othersTrustedContacts.isNotEmpty)) {
|
||||
return Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8),
|
||||
child: DividerWidget(
|
||||
dividerType: DividerType.solid,
|
||||
),
|
||||
),
|
||||
MenuSectionTitle(
|
||||
title: context.l10n.legacyAccounts,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else if (index > 0 &&
|
||||
index <= othersTrustedContacts.length) {
|
||||
final listIndex = index - 1;
|
||||
final currentUser = othersTrustedContacts[listIndex];
|
||||
final isLastItem = index == othersTrustedContacts.length;
|
||||
return Column(
|
||||
children: [
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: currentUser.user.email,
|
||||
makeTextBold: currentUser.isPendingInvite(),
|
||||
subTitle:
|
||||
currentUser.isPendingInvite() ? "⚠" : null,
|
||||
),
|
||||
leadingIconSize: 24.0,
|
||||
leadingIconWidget: UserAvatarWidget(
|
||||
currentUser.user,
|
||||
type: AvatarType.mini,
|
||||
currentUserID: currentUserID,
|
||||
),
|
||||
menuItemColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
if (currentUser.isPendingInvite()) {
|
||||
await showAcceptOrDeclineDialog(
|
||||
context,
|
||||
currentUser,
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return OtherContactPage(
|
||||
contact: currentUser,
|
||||
emergencyInfo: info!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// await routeToPage(
|
||||
// context,
|
||||
// OtherContactPage(
|
||||
// contact: currentUser,
|
||||
// emergencyInfo: info!,
|
||||
// ),
|
||||
// );
|
||||
if (mounted) {
|
||||
unawaited(_fetchData());
|
||||
}
|
||||
}
|
||||
},
|
||||
isTopBorderRadiusRemoved: listIndex > 0,
|
||||
isBottomBorderRadiusRemoved: !isLastItem,
|
||||
singleBorderRadius: 8,
|
||||
surfaceExecutionStates: false,
|
||||
),
|
||||
isLastItem
|
||||
? const SizedBox.shrink()
|
||||
: DividerWidget(
|
||||
dividerType: DividerType.menu,
|
||||
bgColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
childCount: 1 + othersTrustedContacts.length + 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showRevokeOrRemoveDialog(
|
||||
BuildContext context,
|
||||
EmergencyContact contact,
|
||||
) async {
|
||||
if (contact.isPendingInvite()) {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
body:
|
||||
"You have invited ${contact.emergencyContact.email} to be a trusted contact",
|
||||
bodyHighlight: "They are yet to accept your invite",
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).removeInvite,
|
||||
buttonType: ButtonType.critical,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.first,
|
||||
shouldStickToDarkTheme: true,
|
||||
shouldSurfaceExecutionStates: true,
|
||||
shouldShowSuccessConfirmation: false,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance
|
||||
.updateContact(contact, ContactState.userRevokedContact);
|
||||
info?.contacts.remove(contact);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
unawaited(_fetchData());
|
||||
}
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).cancel,
|
||||
buttonType: ButtonType.tertiary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.second,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
body:
|
||||
"You have added ${contact.emergencyContact.email} as a trusted contact",
|
||||
bodyHighlight: "They have accepted your invite",
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).remove,
|
||||
buttonType: ButtonType.critical,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.second,
|
||||
shouldStickToDarkTheme: true,
|
||||
shouldSurfaceExecutionStates: true,
|
||||
shouldShowSuccessConfirmation: false,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance
|
||||
.updateContact(contact, ContactState.userRevokedContact);
|
||||
info?.contacts.remove(contact);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
unawaited(_fetchData());
|
||||
}
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).cancel,
|
||||
buttonType: ButtonType.tertiary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.third,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showAcceptOrDeclineDialog(
|
||||
BuildContext context,
|
||||
EmergencyContact contact,
|
||||
) async {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).acceptTrustInvite,
|
||||
buttonType: ButtonType.primary,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
buttonAction: ButtonAction.first,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance
|
||||
.updateContact(contact, ContactState.contactAccepted);
|
||||
final updatedContact =
|
||||
contact.copyWith(state: ContactState.contactAccepted);
|
||||
info?.othersEmergencyContact.remove(contact);
|
||||
info?.othersEmergencyContact.add(updatedContact);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).declineTrustInvite,
|
||||
buttonType: ButtonType.critical,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.second,
|
||||
shouldStickToDarkTheme: true,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance
|
||||
.updateContact(contact, ContactState.contactDenied);
|
||||
info?.othersEmergencyContact.remove(contact);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).cancel,
|
||||
buttonType: ButtonType.tertiary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.third,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
body: S.of(context).legacyInvite(contact.user.email),
|
||||
actionSheetType: ActionSheetType.defaultActionSheet,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> showRejectRecoveryDialog(RecoverySessions session) async {
|
||||
final String emergencyContactEmail = session.emergencyContact.email;
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: context.l10n.rejectRecovery,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
buttonType: ButtonType.critical,
|
||||
buttonAction: ButtonAction.first,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance.rejectRecovery(session);
|
||||
info?.recoverSessions
|
||||
.removeWhere((element) => element.id == session.id);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
unawaited(_fetchData());
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
if (kDebugMode)
|
||||
ButtonWidget(
|
||||
labelText: "Approve recovery (to be removed)",
|
||||
buttonType: ButtonType.primary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.second,
|
||||
shouldStickToDarkTheme: true,
|
||||
onTap: () async {
|
||||
await EmergencyContactService.instance.approveRecovery(session);
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
unawaited(_fetchData());
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).cancel,
|
||||
buttonType: ButtonType.tertiary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.third,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
body: context.l10n.recoveryWarningBody(emergencyContactEmail),
|
||||
actionSheetType: ActionSheetType.defaultActionSheet,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
275
mobile/lib/emergency/emergency_service.dart
Normal file
@@ -0,0 +1,275 @@
|
||||
import "dart:convert";
|
||||
import "dart:math";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:dio/dio.dart";
|
||||
import "package:flutter/cupertino.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/network/network.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/api/user/srp.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/models/set_keys_request.dart";
|
||||
import "package:photos/services/user_service.dart";
|
||||
import "package:photos/ui/common/user_dialogs.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/email_util.dart";
|
||||
import "package:pointycastle/pointycastle.dart";
|
||||
import "package:pointycastle/random/fortuna_random.dart";
|
||||
import "package:pointycastle/srp/srp6_client.dart";
|
||||
import "package:pointycastle/srp/srp6_standard_groups.dart";
|
||||
import "package:pointycastle/srp/srp6_util.dart";
|
||||
import "package:pointycastle/srp/srp6_verifier_generator.dart";
|
||||
import "package:uuid/uuid.dart";
|
||||
|
||||
class EmergencyContactService {
|
||||
late Dio _enteDio;
|
||||
late UserService _userService;
|
||||
late Configuration _config;
|
||||
late final Logger _logger = Logger("EmergencyContactService");
|
||||
|
||||
EmergencyContactService._privateConstructor() {
|
||||
_enteDio = NetworkClient.instance.enteDio;
|
||||
_userService = UserService.instance;
|
||||
_config = Configuration.instance;
|
||||
}
|
||||
|
||||
static final EmergencyContactService instance =
|
||||
EmergencyContactService._privateConstructor();
|
||||
|
||||
Future<bool> addContact(BuildContext context, String email) async {
|
||||
if (!isValidEmail(email)) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
S.of(context).invalidEmailAddress,
|
||||
S.of(context).enterValidEmail,
|
||||
);
|
||||
return false;
|
||||
} else if (email.trim() == Configuration.instance.getEmail()) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
S.of(context).oops,
|
||||
S.of(context).youCannotShareWithYourself,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
final String? publicKey = await _userService.getPublicKey(email);
|
||||
if (publicKey == null) {
|
||||
await showInviteDialog(context, email);
|
||||
return false;
|
||||
}
|
||||
final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
|
||||
final encryptedKey = CryptoUtil.sealSync(
|
||||
recoveryKey,
|
||||
CryptoUtil.base642bin(publicKey),
|
||||
);
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/add",
|
||||
data: {
|
||||
"email": email.trim(),
|
||||
"encryptedKey": CryptoUtil.bin2base64(encryptedKey),
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<EmergencyInfo> getInfo() async {
|
||||
try {
|
||||
final response = await _enteDio.get("/emergency-contacts/info");
|
||||
return EmergencyInfo.fromJson(response.data);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to get info', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateContact(
|
||||
EmergencyContact contact,
|
||||
ContactState state,
|
||||
) async {
|
||||
try {
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/update",
|
||||
data: {
|
||||
"userID": contact.user.id,
|
||||
"emergencyContactID": contact.emergencyContact.id,
|
||||
"state": state.stringValue,
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to update contact', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> startRecovery(EmergencyContact contact) async {
|
||||
try {
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/start-recovery",
|
||||
data: {
|
||||
"userID": contact.user.id,
|
||||
"emergencyContactID": contact.emergencyContact.id,
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to start recovery', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> stopRecovery(RecoverySessions session) async {
|
||||
try {
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/stop-recovery",
|
||||
data: {
|
||||
"userID": session.user.id,
|
||||
"emergencyContactID": session.emergencyContact.id,
|
||||
"id": session.id,
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to stop recovery', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> rejectRecovery(RecoverySessions session) async {
|
||||
try {
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/reject-recovery",
|
||||
data: {
|
||||
"userID": session.user.id,
|
||||
"emergencyContactID": session.emergencyContact.id,
|
||||
"id": session.id,
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to stop recovery', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> approveRecovery(RecoverySessions session) async {
|
||||
try {
|
||||
await _enteDio.post(
|
||||
"/emergency-contacts/approve-recovery",
|
||||
data: {
|
||||
"userID": session.user.id,
|
||||
"emergencyContactID": session.emergencyContact.id,
|
||||
"id": session.id,
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to approve recovery', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(String, KeyAttributes)> getRecoveryInfo(
|
||||
RecoverySessions sessions,
|
||||
) async {
|
||||
try {
|
||||
final resp = await _enteDio.get(
|
||||
"/emergency-contacts/recovery-info/${sessions.id}",
|
||||
);
|
||||
final String encryptedKey = resp.data["encryptedKey"]!;
|
||||
final decryptedKey = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(encryptedKey),
|
||||
CryptoUtil.base642bin(_config.getKeyAttributes()!.publicKey),
|
||||
_config.getSecretKey()!,
|
||||
);
|
||||
final String hexRecoveryKey = CryptoUtil.bin2hex(decryptedKey);
|
||||
final KeyAttributes keyAttributes =
|
||||
KeyAttributes.fromMap(resp.data['userKeyAttr']);
|
||||
return (hexRecoveryKey, keyAttributes);
|
||||
} catch (e, s) {
|
||||
Logger("EmergencyContact").severe('failed to stop recovery', e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> changePasswordForOther(
|
||||
Uint8List loginKey,
|
||||
SetKeysRequest setKeysRequest,
|
||||
RecoverySessions recoverySessions,
|
||||
) async {
|
||||
try {
|
||||
final SRP6GroupParameters kDefaultSrpGroup =
|
||||
SRP6StandardGroups.rfc5054_4096;
|
||||
final String username = const Uuid().v4().toString();
|
||||
final SecureRandom random = _getSecureRandom();
|
||||
final Uint8List identity = Uint8List.fromList(utf8.encode(username));
|
||||
final Uint8List password = loginKey;
|
||||
final Uint8List salt = random.nextBytes(16);
|
||||
final gen = SRP6VerifierGenerator(
|
||||
group: kDefaultSrpGroup,
|
||||
digest: Digest('SHA-256'),
|
||||
);
|
||||
final v = gen.generateVerifier(salt, identity, password);
|
||||
|
||||
final client = SRP6Client(
|
||||
group: kDefaultSrpGroup,
|
||||
digest: Digest('SHA-256'),
|
||||
random: random,
|
||||
);
|
||||
|
||||
final A = client.generateClientCredentials(salt, identity, password);
|
||||
final request = SetupSRPRequest(
|
||||
srpUserID: username,
|
||||
srpSalt: base64Encode(salt),
|
||||
srpVerifier: base64Encode(SRP6Util.encodeBigInt(v)),
|
||||
srpA: base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||
isUpdate: false,
|
||||
);
|
||||
final response = await _enteDio.post(
|
||||
"/emergency-contacts/init-change-password",
|
||||
data: {
|
||||
"recoveryID": recoverySessions.id,
|
||||
"setupSRPRequest": request.toMap(),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final SetupSRPResponse setupSRPResponse =
|
||||
SetupSRPResponse.fromJson(response.data);
|
||||
final serverB =
|
||||
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
|
||||
|
||||
// ignore: unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
// ignore: unused_local_variable
|
||||
late Response srpCompleteResponse;
|
||||
srpCompleteResponse = await _enteDio.post(
|
||||
"/emergency-contacts/change-password",
|
||||
data: {
|
||||
"recoveryID": recoverySessions.id,
|
||||
'updateSrpAndKeysRequest': {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
|
||||
'updatedKeyAttr': setKeysRequest.toMap(),
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw Exception("register-srp action failed");
|
||||
}
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to change password for other", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
SecureRandom _getSecureRandom() {
|
||||
final List<int> seeds = [];
|
||||
final random = Random.secure();
|
||||
for (int i = 0; i < 32; i++) {
|
||||
seeds.add(random.nextInt(255));
|
||||
}
|
||||
final secureRandom = FortunaRandom();
|
||||
secureRandom.seed(KeyParameter(Uint8List.fromList(seeds)));
|
||||
return secureRandom;
|
||||
}
|
||||
}
|
||||
153
mobile/lib/emergency/model.dart
Normal file
@@ -0,0 +1,153 @@
|
||||
import "package:photos/models/api/collection/user.dart";
|
||||
|
||||
enum ContactState {
|
||||
userInvitedContact,
|
||||
userRevokedContact,
|
||||
contactAccepted,
|
||||
contactLeft,
|
||||
contactDenied,
|
||||
unknown,
|
||||
}
|
||||
|
||||
extension ContactStateExtension on ContactState {
|
||||
String get stringValue {
|
||||
switch (this) {
|
||||
case ContactState.userInvitedContact:
|
||||
return "INVITED";
|
||||
case ContactState.userRevokedContact:
|
||||
return "REVOKED";
|
||||
case ContactState.contactAccepted:
|
||||
return "ACCEPTED";
|
||||
case ContactState.contactLeft:
|
||||
return "CONTACT_LEFT";
|
||||
case ContactState.contactDenied:
|
||||
return "CONTACT_DENIED";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static ContactState fromString(String value) {
|
||||
switch (value) {
|
||||
case "INVITED":
|
||||
return ContactState.userInvitedContact;
|
||||
case "REVOKED":
|
||||
return ContactState.userRevokedContact;
|
||||
case "ACCEPTED":
|
||||
return ContactState.contactAccepted;
|
||||
case "CONTACT_LEFT":
|
||||
return ContactState.contactLeft;
|
||||
case "CONTACT_DENIED":
|
||||
return ContactState.contactDenied;
|
||||
default:
|
||||
return ContactState.unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EmergencyContact {
|
||||
final User user;
|
||||
final User emergencyContact;
|
||||
final ContactState state;
|
||||
final int recoveryNoticeInDays;
|
||||
|
||||
EmergencyContact(
|
||||
this.user,
|
||||
this.emergencyContact,
|
||||
this.state,
|
||||
this.recoveryNoticeInDays,
|
||||
);
|
||||
|
||||
// copyWith
|
||||
EmergencyContact copyWith({
|
||||
User? user,
|
||||
User? emergencyContact,
|
||||
ContactState? state,
|
||||
int? recoveryNoticeInDays,
|
||||
}) {
|
||||
return EmergencyContact(
|
||||
user ?? this.user,
|
||||
emergencyContact ?? this.emergencyContact,
|
||||
state ?? this.state,
|
||||
recoveryNoticeInDays ?? this.recoveryNoticeInDays,
|
||||
);
|
||||
}
|
||||
|
||||
// fromJson
|
||||
EmergencyContact.fromJson(Map<String, dynamic> json)
|
||||
: user = User.fromMap(json['user']),
|
||||
emergencyContact = User.fromMap(json['emergencyContact']),
|
||||
state = ContactStateExtension.fromString(json['state'] as String),
|
||||
recoveryNoticeInDays = json['recoveryNoticeInDays'];
|
||||
|
||||
bool isCurrentUserContact(int userID) {
|
||||
return user.id == userID;
|
||||
}
|
||||
|
||||
bool isPendingInvite() {
|
||||
return state == ContactState.userInvitedContact;
|
||||
}
|
||||
}
|
||||
|
||||
class EmergencyInfo {
|
||||
// List of emergency contacts added by the user
|
||||
final List<EmergencyContact> contacts;
|
||||
|
||||
// List of recovery sessions that are created to recover current user account
|
||||
final List<RecoverySessions> recoverSessions;
|
||||
|
||||
// List of emergency contacts that have added current user as their emergency contact
|
||||
final List<EmergencyContact> othersEmergencyContact;
|
||||
|
||||
// List of recovery sessions that are created to recover grantor's account
|
||||
final List<RecoverySessions> othersRecoverySession;
|
||||
|
||||
EmergencyInfo(
|
||||
this.contacts,
|
||||
this.recoverSessions,
|
||||
this.othersEmergencyContact,
|
||||
this.othersRecoverySession,
|
||||
);
|
||||
|
||||
// from json
|
||||
EmergencyInfo.fromJson(Map<String, dynamic> json)
|
||||
: contacts = (json['contacts'] as List)
|
||||
.map((contact) => EmergencyContact.fromJson(contact))
|
||||
.toList(),
|
||||
recoverSessions = (json['recoverSessions'] as List)
|
||||
.map((session) => RecoverySessions.fromJson(session))
|
||||
.toList(),
|
||||
othersEmergencyContact = (json['othersEmergencyContact'] as List)
|
||||
.map((grantor) => EmergencyContact.fromJson(grantor))
|
||||
.toList(),
|
||||
othersRecoverySession = (json['othersRecoverySession'] as List)
|
||||
.map((session) => RecoverySessions.fromJson(session))
|
||||
.toList();
|
||||
}
|
||||
|
||||
class RecoverySessions {
|
||||
final String id;
|
||||
final User user;
|
||||
final User emergencyContact;
|
||||
final String status;
|
||||
final int waitTill;
|
||||
final int createdAt;
|
||||
|
||||
RecoverySessions(
|
||||
this.id,
|
||||
this.user,
|
||||
this.emergencyContact,
|
||||
this.status,
|
||||
this.waitTill,
|
||||
this.createdAt,
|
||||
);
|
||||
|
||||
// fromJson
|
||||
RecoverySessions.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
user = User.fromMap(json['user']),
|
||||
emergencyContact = User.fromMap(json['emergencyContact']),
|
||||
status = json['status'],
|
||||
waitTill = json['waitTill'],
|
||||
createdAt = json['createdAt'];
|
||||
}
|
||||
279
mobile/lib/emergency/other_contact_page.dart
Normal file
@@ -0,0 +1,279 @@
|
||||
import "package:collection/collection.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/emergency/emergency_service.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/emergency/recover_others_account.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/l10n/l10n.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/theme/colors.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/action_sheet_widget.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/captioned_text_widget.dart";
|
||||
import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
|
||||
import "package:photos/ui/components/menu_section_title.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/utils/date_time_util.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/navigation_util.dart";
|
||||
|
||||
// OtherContactPage is used to start recovery process for other user's account
|
||||
// Based on the state of the contact & recovery session, it will show
|
||||
// different UI
|
||||
class OtherContactPage extends StatefulWidget {
|
||||
final EmergencyContact contact;
|
||||
final EmergencyInfo emergencyInfo;
|
||||
|
||||
const OtherContactPage({
|
||||
required this.contact,
|
||||
required this.emergencyInfo,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OtherContactPage> createState() => _OtherContactPageState();
|
||||
}
|
||||
|
||||
class _OtherContactPageState extends State<OtherContactPage> {
|
||||
late String accountEmail = widget.contact.user.email;
|
||||
RecoverySessions? recoverySession;
|
||||
String? waitTill;
|
||||
final Logger _logger = Logger("_OtherContactPageState");
|
||||
late EmergencyInfo emergencyInfo = widget.emergencyInfo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
recoverySession = widget.emergencyInfo.othersRecoverySession
|
||||
.firstWhereOrNull((session) => session.user.email == accountEmail);
|
||||
_fetchData();
|
||||
}
|
||||
|
||||
Future<void> _fetchData() async {
|
||||
try {
|
||||
final result = await EmergencyContactService.instance.getInfo();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
recoverySession = result.othersRecoverySession.firstWhereOrNull(
|
||||
(session) => session.user.email == accountEmail,
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.severe("Error fetching data", e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_logger.info('session ${widget.emergencyInfo}');
|
||||
if (recoverySession != null) {
|
||||
final dateTime = DateTime.now().add(
|
||||
Duration(
|
||||
microseconds: recoverySession!.waitTill,
|
||||
),
|
||||
);
|
||||
waitTill = getFormattedTime(context, dateTime);
|
||||
}
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
TitleBarTitleWidget(
|
||||
title: context.l10n.recoverAccount,
|
||||
),
|
||||
Text(
|
||||
accountEmail,
|
||||
textAlign: TextAlign.left,
|
||||
style:
|
||||
textTheme.small.copyWith(color: colorScheme.textMuted),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
recoverySession == null
|
||||
? Text(
|
||||
"You can recover $accountEmail's account in ${widget.contact.recoveryNoticeInDays} days"
|
||||
" after starting the recovery process.",
|
||||
style: textTheme.body,
|
||||
)
|
||||
: (recoverySession!.status == "READY"
|
||||
? Text(
|
||||
context.l10n.recoveryReady(accountEmail),
|
||||
style: textTheme.body,
|
||||
)
|
||||
: Text(
|
||||
"You can recover $accountEmail's"
|
||||
" account after $waitTill.",
|
||||
style: textTheme.bodyBold,
|
||||
)),
|
||||
const SizedBox(height: 24),
|
||||
if (recoverySession == null)
|
||||
ButtonWidget(
|
||||
// icon: Icons.start_outlined,
|
||||
buttonType: ButtonType.trailingIconPrimary,
|
||||
icon: Icons.start_outlined,
|
||||
labelText: S.of(context).startAccountRecoveryTitle,
|
||||
onTap: widget.contact.isPendingInvite()
|
||||
? null
|
||||
: () async {
|
||||
final actionResult = await showChoiceActionSheet(
|
||||
context,
|
||||
title: S.of(context).startAccountRecoveryTitle,
|
||||
firstButtonLabel: S.of(context).yes,
|
||||
body: "Are you sure you want to initiate recovery?",
|
||||
isCritical: true,
|
||||
);
|
||||
if (actionResult?.action != null) {
|
||||
if (actionResult!.action == ButtonAction.first) {
|
||||
try {
|
||||
await EmergencyContactService.instance
|
||||
.startRecovery(widget.contact);
|
||||
if (mounted) {
|
||||
_fetchData().ignore();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.recoveryInitiated,
|
||||
context.l10n.recoveryInitiatedDesc(
|
||||
widget.contact.recoveryNoticeInDays,
|
||||
Configuration.instance.getEmail()!,
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context: context, error: e)
|
||||
.ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// isTopBorderRadiusRemoved: true,
|
||||
),
|
||||
if (recoverySession != null && recoverySession!.status == "READY")
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12.0),
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
labelText: context.l10n.recoverAccount,
|
||||
onTap: () async {
|
||||
try {
|
||||
final (String key, KeyAttributes attributes) =
|
||||
await EmergencyContactService.instance
|
||||
.getRecoveryInfo(recoverySession!);
|
||||
routeToPage(
|
||||
context,
|
||||
RecoverOthersAccount(key, attributes, recoverySession!),
|
||||
).ignore();
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context: context, error: e)
|
||||
.ignore();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (recoverySession != null &&
|
||||
(recoverySession!.status == "WAITING" ||
|
||||
recoverySession!.status == "READY"))
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: S.of(context).cancelAccountRecovery,
|
||||
shouldSurfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
final actionResult = await showChoiceActionSheet(
|
||||
context,
|
||||
title: S.of(context).cancelAccountRecovery,
|
||||
firstButtonLabel: S.of(context).yes,
|
||||
body: S.of(context).cancelAccountRecoveryBody,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
await EmergencyContactService.instance
|
||||
.stopRecovery(recoverySession!);
|
||||
},
|
||||
);
|
||||
if (actionResult?.action == ButtonAction.first) {
|
||||
_fetchData().ignore();
|
||||
}
|
||||
},
|
||||
),
|
||||
SizedBox(height: recoverySession == null ? 48 : 24),
|
||||
MenuSectionTitle(
|
||||
title: S.of(context).removeYourselfAsTrustedContact,
|
||||
),
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).remove,
|
||||
textColor: warning500,
|
||||
makeTextBold: true,
|
||||
),
|
||||
leadingIcon: Icons.not_interested_outlined,
|
||||
leadingIconColor: warning500,
|
||||
menuItemColor: getEnteColorScheme(context).fillFaint,
|
||||
surfaceExecutionStates: false,
|
||||
onTap: () async {
|
||||
await showRemoveSheet();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showRemoveSheet() async {
|
||||
await showActionSheet(
|
||||
context: context,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
labelText: context.l10n.remove,
|
||||
buttonSize: ButtonSize.large,
|
||||
shouldStickToDarkTheme: true,
|
||||
buttonType: ButtonType.critical,
|
||||
buttonAction: ButtonAction.first,
|
||||
onTap: () async {
|
||||
try {
|
||||
await EmergencyContactService.instance.updateContact(
|
||||
widget.contact,
|
||||
ContactState.contactLeft,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
showGenericErrorDialog(context: context, error: e).ignore();
|
||||
}
|
||||
},
|
||||
isInAlert: true,
|
||||
),
|
||||
ButtonWidget(
|
||||
labelText: S.of(context).cancel,
|
||||
buttonType: ButtonType.tertiary,
|
||||
buttonSize: ButtonSize.large,
|
||||
buttonAction: ButtonAction.third,
|
||||
shouldStickToDarkTheme: true,
|
||||
isInAlert: true,
|
||||
),
|
||||
],
|
||||
body: "Are you sure your want to stop being a trusted "
|
||||
"contact for $accountEmail?",
|
||||
title: context.l10n.remove,
|
||||
actionSheetType: ActionSheetType.defaultActionSheet,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
364
mobile/lib/emergency/recover_others_account.dart
Normal file
@@ -0,0 +1,364 @@
|
||||
import "dart:convert";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:password_strength/password_strength.dart';
|
||||
import "package:photos/emergency/emergency_service.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/key_attributes.dart";
|
||||
import "package:photos/models/set_keys_request.dart";
|
||||
import 'package:photos/ui/common/dynamic_fab.dart';
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import 'package:photos/utils/dialog_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
|
||||
class RecoverOthersAccount extends StatefulWidget {
|
||||
final String recoveryKey;
|
||||
final KeyAttributes attributes;
|
||||
final RecoverySessions sessions;
|
||||
|
||||
const RecoverOthersAccount(
|
||||
this.recoveryKey,
|
||||
this.attributes,
|
||||
this.sessions, {
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecoverOthersAccount> createState() => _RecoverOthersAccountState();
|
||||
}
|
||||
|
||||
class _RecoverOthersAccountState extends State<RecoverOthersAccount> {
|
||||
static const kMildPasswordStrengthThreshold = 0.4;
|
||||
static const kStrongPasswordStrengthThreshold = 0.7;
|
||||
|
||||
final _logger = Logger((_RecoverOthersAccountState).toString());
|
||||
final _passwordController1 = TextEditingController(),
|
||||
_passwordController2 = TextEditingController();
|
||||
final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
|
||||
String _passwordInInputBox = '';
|
||||
String _passwordInInputConfirmationBox = '';
|
||||
double _passwordStrength = 0.0;
|
||||
bool _password1Visible = false;
|
||||
bool _password2Visible = false;
|
||||
final _password1FocusNode = FocusNode();
|
||||
final _password2FocusNode = FocusNode();
|
||||
bool _password1InFocus = false;
|
||||
bool _password2InFocus = false;
|
||||
|
||||
bool _passwordsMatch = false;
|
||||
bool _isPasswordValid = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_password1FocusNode.addListener(() {
|
||||
setState(() {
|
||||
_password1InFocus = _password1FocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
_password2FocusNode.addListener(() {
|
||||
setState(() {
|
||||
_password2InFocus = _password2FocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
|
||||
FloatingActionButtonLocation? fabLocation() {
|
||||
if (isKeypadOpen) {
|
||||
return null;
|
||||
} else {
|
||||
return FloatingActionButtonLocation.centerFloat;
|
||||
}
|
||||
}
|
||||
|
||||
String title = S.of(context).setPasswordTitle;
|
||||
title = S.of(context).resetPasswordTitle;
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: isKeypadOpen,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
body: _getBody(title),
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordsMatch && _isPasswordValid,
|
||||
buttonText: title,
|
||||
onPressedFunction: () {
|
||||
_updatePassword();
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBody(String buttonTextAndHeading) {
|
||||
final email = widget.sessions.user.email;
|
||||
var passwordStrengthText = S.of(context).weakStrength;
|
||||
var passwordStrengthColor = Colors.redAccent;
|
||||
if (_passwordStrength > kStrongPasswordStrengthThreshold) {
|
||||
passwordStrengthText = S.of(context).strongStrength;
|
||||
passwordStrengthColor = Colors.greenAccent;
|
||||
} else if (_passwordStrength > kMildPasswordStrengthThreshold) {
|
||||
passwordStrengthText = S.of(context).moderateStrength;
|
||||
passwordStrengthColor = Colors.orangeAccent;
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AutofillGroup(
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
buttonTextAndHeading,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
"Enter new password for $email account. You will be able "
|
||||
"to use this password to login into $email account.",
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
),
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(12)),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
// password
|
||||
visible: false,
|
||||
child: TextFormField(
|
||||
autofillHints: const [
|
||||
AutofillHints.email,
|
||||
],
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
initialValue: email,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
decoration: InputDecoration(
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: S.of(context).password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: _password1InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password1Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password1Visible = !_password1Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
obscureText: !_password1Visible,
|
||||
controller: _passwordController1,
|
||||
autofocus: false,
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
onChanged: (password) {
|
||||
setState(() {
|
||||
_passwordInInputBox = password;
|
||||
_passwordStrength = estimatePasswordStrength(password);
|
||||
_isPasswordValid =
|
||||
_passwordStrength >= kMildPasswordStrengthThreshold;
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
});
|
||||
},
|
||||
textInputAction: TextInputAction.next,
|
||||
focusNode: _password1FocusNode,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.visiblePassword,
|
||||
controller: _passwordController2,
|
||||
obscureText: !_password2Visible,
|
||||
autofillHints: const [AutofillHints.newPassword],
|
||||
onEditingComplete: () => TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: _passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: S.of(context).confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
),
|
||||
suffixIcon: _password2InFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_password2Visible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_password2Visible = !_password2Visible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
focusNode: _password2FocusNode,
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != '') {
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Opacity(
|
||||
opacity:
|
||||
(_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Text(
|
||||
S.of(context).passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Padding(padding: EdgeInsets.all(20)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _updatePassword() async {
|
||||
final dialog =
|
||||
createProgressDialog(context, S.of(context).generatingEncryptionKeys);
|
||||
await dialog.show();
|
||||
try {
|
||||
final String password = _passwordController1.text;
|
||||
final KeyAttributes attributes = widget.attributes;
|
||||
Uint8List? masterKey;
|
||||
try {
|
||||
// Decrypt the master key that was earlier encrypted with the recovery key
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
CryptoUtil.base642bin(attributes.masterKeyEncryptedWithRecoveryKey!),
|
||||
CryptoUtil.hex2bin(widget.recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce!),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e, "Failed to get master key using recoveryKey");
|
||||
rethrow;
|
||||
}
|
||||
|
||||
// Derive a key from the password that will be used to encrypt and
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
// Encrypt the key with this derived key
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
final updatedAttributes = attributes.copyWith(
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
final setKeyRequest = SetKeysRequest(
|
||||
kekSalt: updatedAttributes.kekSalt,
|
||||
encryptedKey: updatedAttributes.encryptedKey,
|
||||
keyDecryptionNonce: updatedAttributes.keyDecryptionNonce,
|
||||
memLimit: updatedAttributes.memLimit!,
|
||||
opsLimit: updatedAttributes.opsLimit!,
|
||||
);
|
||||
await EmergencyContactService.instance.changePasswordForOther(
|
||||
loginKey,
|
||||
setKeyRequest,
|
||||
widget.sessions,
|
||||
);
|
||||
await dialog.hide();
|
||||
showShortToast(context, S.of(context).passwordChangedSuccessfully);
|
||||
Navigator.of(context).pop();
|
||||
} catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
await dialog.hide();
|
||||
showGenericErrorDialog(context: context, error: e).ignore();
|
||||
}
|
||||
}
|
||||
}
|
||||
364
mobile/lib/emergency/select_contact_page.dart
Normal file
@@ -0,0 +1,364 @@
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
import 'package:photos/core/configuration.dart';
|
||||
import "package:photos/emergency/emergency_service.dart";
|
||||
import "package:photos/emergency/model.dart";
|
||||
import "package:photos/generated/l10n.dart";
|
||||
import "package:photos/models/api/collection/user.dart";
|
||||
import 'package:photos/services/collections_service.dart';
|
||||
import "package:photos/services/user_service.dart";
|
||||
import 'package:photos/theme/ente_theme.dart';
|
||||
import 'package:photos/ui/actions/collection/collection_sharing_actions.dart';
|
||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||
import 'package:photos/ui/components/divider_widget.dart';
|
||||
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/menu_section_description_widget.dart';
|
||||
import 'package:photos/ui/components/menu_section_title.dart';
|
||||
import 'package:photos/ui/components/models/button_type.dart';
|
||||
import 'package:photos/ui/sharing/user_avator_widget.dart';
|
||||
import "package:photos/ui/sharing/verify_identity_dialog.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
|
||||
class AddContactPage extends StatefulWidget {
|
||||
final EmergencyInfo emergencyInfo;
|
||||
|
||||
const AddContactPage(this.emergencyInfo, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _AddContactPage();
|
||||
}
|
||||
|
||||
class _AddContactPage extends State<AddContactPage> {
|
||||
String selectedEmail = '';
|
||||
String _email = '';
|
||||
bool isEmailListEmpty = false;
|
||||
bool _emailIsValid = false;
|
||||
bool isKeypadOpen = false;
|
||||
late CollectionActions collectionActions;
|
||||
late final Logger _logger = Logger('AddContactPage');
|
||||
|
||||
// Focus nodes are necessary
|
||||
final textFieldFocusNode = FocusNode();
|
||||
final _textController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
collectionActions = CollectionActions(CollectionsService.instance);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
final enteTextTheme = getEnteTextTheme(context);
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
final List<User> suggestedUsers = _getSuggestedUser();
|
||||
isEmailListEmpty = suggestedUsers.isEmpty;
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: isKeypadOpen,
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
S.of(context).addTrustedContact,
|
||||
),
|
||||
),
|
||||
body: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Text(
|
||||
S.of(context).addANewEmail,
|
||||
style: enteTextTheme.small
|
||||
.copyWith(color: enteColorScheme.textMuted),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: _getEmailField(),
|
||||
),
|
||||
if (isEmailListEmpty)
|
||||
const Expanded(child: SizedBox.shrink())
|
||||
else
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
!isEmailListEmpty
|
||||
? MenuSectionTitle(
|
||||
title: S.of(context).orPickAnExistingOne,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= suggestedUsers.length) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: MenuSectionDescriptionWidget(
|
||||
content: S.of(context).whyAddTrustContact,
|
||||
),
|
||||
);
|
||||
}
|
||||
final currentUser = suggestedUsers[index];
|
||||
return Column(
|
||||
children: [
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: currentUser.email,
|
||||
),
|
||||
leadingIconSize: 24.0,
|
||||
leadingIconWidget: UserAvatarWidget(
|
||||
currentUser,
|
||||
type: AvatarType.mini,
|
||||
),
|
||||
menuItemColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
pressedColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon:
|
||||
(selectedEmail == currentUser.email)
|
||||
? Icons.check
|
||||
: null,
|
||||
onTap: () async {
|
||||
textFieldFocusNode.unfocus();
|
||||
if (selectedEmail == currentUser.email) {
|
||||
selectedEmail = '';
|
||||
} else {
|
||||
selectedEmail = currentUser.email;
|
||||
}
|
||||
setState(() => {});
|
||||
},
|
||||
isTopBorderRadiusRemoved: index > 0,
|
||||
isBottomBorderRadiusRemoved:
|
||||
index < (suggestedUsers.length - 1),
|
||||
),
|
||||
(index == (suggestedUsers.length - 1))
|
||||
? const SizedBox.shrink()
|
||||
: DividerWidget(
|
||||
dividerType: DividerType.menu,
|
||||
bgColor:
|
||||
getEnteColorScheme(context).fillFaint,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: suggestedUsers.length + 1,
|
||||
// physics: const ClampingScrollPhysics(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8,
|
||||
bottom: 8,
|
||||
left: 16,
|
||||
right: 16,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.primary,
|
||||
buttonSize: ButtonSize.large,
|
||||
labelText: "Add",
|
||||
isDisabled: (selectedEmail == '' && !_emailIsValid),
|
||||
onTap: (selectedEmail == '' && !_emailIsValid)
|
||||
? null
|
||||
: () async {
|
||||
final emailToAdd =
|
||||
selectedEmail == '' ? _email : selectedEmail;
|
||||
final choiceResult = await showChoiceActionSheet(
|
||||
context,
|
||||
title: S.of(context).warning,
|
||||
body: S.of(context).confirmAddingTrustedContact(
|
||||
emailToAdd,
|
||||
30,
|
||||
),
|
||||
firstButtonLabel: S.of(context).proceed,
|
||||
isCritical: true,
|
||||
);
|
||||
if (choiceResult != null &&
|
||||
choiceResult.action == ButtonAction.first) {
|
||||
try {
|
||||
final r = await EmergencyContactService.instance
|
||||
.addContact(context, emailToAdd);
|
||||
if (r && mounted) {
|
||||
Navigator.of(context).pop(true);
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.severe('Failed to add contact', e);
|
||||
await showErrorDialog(
|
||||
context,
|
||||
S.of(context).error,
|
||||
S.of(context).somethingWentWrong,
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
if ((selectedEmail == '' && !_emailIsValid)) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
S.of(context).invalidEmailAddress,
|
||||
S.of(context).enterValidEmail,
|
||||
);
|
||||
return;
|
||||
}
|
||||
final emailToAdd =
|
||||
selectedEmail == '' ? _email : selectedEmail;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return VerifyIdentifyDialog(
|
||||
self: false,
|
||||
email: emailToAdd,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
S.of(context).verifyIDLabel,
|
||||
textAlign: TextAlign.center,
|
||||
style: enteTextTheme.smallMuted.copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void clearFocus() {
|
||||
_textController.clear();
|
||||
_email = _textController.text;
|
||||
_emailIsValid = false;
|
||||
textFieldFocusNode.unfocus();
|
||||
setState(() => {});
|
||||
}
|
||||
|
||||
Widget _getEmailField() {
|
||||
return TextFormField(
|
||||
controller: _textController,
|
||||
focusNode: textFieldFocusNode,
|
||||
style: getEnteTextTheme(context).body,
|
||||
autofillHints: const [AutofillHints.email],
|
||||
decoration: InputDecoration(
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
||||
borderSide:
|
||||
BorderSide(color: getEnteColorScheme(context).strokeMuted),
|
||||
),
|
||||
fillColor: getEnteColorScheme(context).fillFaint,
|
||||
filled: true,
|
||||
hintText: S.of(context).enterEmail,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
prefixIcon: Icon(
|
||||
Icons.email_outlined,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
suffixIcon: _email == ''
|
||||
? null
|
||||
: IconButton(
|
||||
onPressed: clearFocus,
|
||||
icon: Icon(
|
||||
Icons.cancel,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
if (selectedEmail != '') {
|
||||
selectedEmail = '';
|
||||
}
|
||||
_email = value.trim();
|
||||
_emailIsValid = EmailValidator.validate(_email);
|
||||
setState(() {});
|
||||
},
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
//initialValue: _email,
|
||||
textInputAction: TextInputAction.next,
|
||||
);
|
||||
}
|
||||
|
||||
List<User> _getSuggestedUser() {
|
||||
final List<User> suggestedUsers = [];
|
||||
final Set<String> existingEmails = {};
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
existingEmails.add(Configuration.instance.getEmail()!);
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
!existingEmails.contains(u.email)) {
|
||||
existingEmails.add(u.email);
|
||||
suggestedUsers.add(u);
|
||||
}
|
||||
}
|
||||
} else if (c.owner != null &&
|
||||
c.owner!.id != null &&
|
||||
c.owner!.email.isNotEmpty &&
|
||||
!existingEmails.contains(c.owner!.email)) {
|
||||
existingEmails.add(c.owner!.email);
|
||||
suggestedUsers.add(c.owner!);
|
||||
}
|
||||
}
|
||||
final cachedUserDetails = UserService.instance.getCachedUserDetails();
|
||||
if (cachedUserDetails != null &&
|
||||
(cachedUserDetails.familyData?.members?.isNotEmpty ?? false)) {
|
||||
for (final member in cachedUserDetails.familyData!.members!) {
|
||||
if (!existingEmails.contains(member.email)) {
|
||||
existingEmails.add(member.email);
|
||||
suggestedUsers.add(User(email: member.email));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_textController.text.trim().isNotEmpty) {
|
||||
suggestedUsers.removeWhere(
|
||||
(element) => !element.email
|
||||
.toLowerCase()
|
||||
.contains(_textController.text.trim().toLowerCase()),
|
||||
);
|
||||
}
|
||||
suggestedUsers.sort((a, b) => a.email.compareTo(b.email));
|
||||
|
||||
return suggestedUsers;
|
||||
}
|
||||
}
|
||||
16
mobile/lib/generated/intl/messages_ar.dart
generated
@@ -26,10 +26,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("مرحبًا مجددًا!"),
|
||||
"ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage(
|
||||
"أُدركُ أنّني فقدتُ كلمة مروري، فقد أفقد بياناتي لأن بياناتي <underline>مشفرة تشفيرًا تامًّا من النهاية إلى النهاية</underline>."),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("إلغاء"),
|
||||
"decrypting": MessageLookupByLibrary.simpleMessage("فك التشفير..."),
|
||||
"email": MessageLookupByLibrary.simpleMessage("البريد الإلكتروني"),
|
||||
@@ -37,8 +33,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("أدخل عنوان بريدك الإلكتروني"),
|
||||
"enterYourRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("أدخل رمز الاسترداد"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"forgotPassword":
|
||||
MessageLookupByLibrary.simpleMessage("نسيت كلمة المرور"),
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -51,23 +45,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("ما من مفتاح استرداد؟"),
|
||||
"noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage(
|
||||
"لا يمكن فك تشفير بياناتك دون كلمة المرور أو مفتاح الاسترداد بسبب طبيعة بروتوكول التشفير الخاص بنا من النهاية إلى النهاية"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"recoverButton": MessageLookupByLibrary.simpleMessage("استرداد"),
|
||||
"recoverySuccessful":
|
||||
MessageLookupByLibrary.simpleMessage("نجح الاسترداد!"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"sorry": MessageLookupByLibrary.simpleMessage("المعذرة"),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("إنهاء"),
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("إنهاء الجلسة؟"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("هذا الجهاز"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
|
||||
16
mobile/lib/generated/intl/messages_be.dart
generated
@@ -44,14 +44,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"after1Week": MessageLookupByLibrary.simpleMessage("Праз 1 тыдзень"),
|
||||
"after1Year": MessageLookupByLibrary.simpleMessage("Праз 1 год"),
|
||||
"albumOwner": MessageLookupByLibrary.simpleMessage("Уладальнік"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"apply": MessageLookupByLibrary.simpleMessage("Ужыць"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Якая асноўная прычына выдалення вашага ўліковага запісу?"),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Рэзервовая копія"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Скасаваць"),
|
||||
"change": MessageLookupByLibrary.simpleMessage("Змяніць"),
|
||||
"changeEmail": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -132,8 +128,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Увядзіце свой пароль"),
|
||||
"enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Увядзіце свой ключ аднаўлення"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"familyPlans":
|
||||
MessageLookupByLibrary.simpleMessage("Сямейныя тарыфныя планы"),
|
||||
"faqs": MessageLookupByLibrary.simpleMessage("Частыя пытанні"),
|
||||
@@ -181,11 +175,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"notifications": MessageLookupByLibrary.simpleMessage("Апавяшчэнні"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("Добра"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Вой"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Пароль"),
|
||||
"passwordChangedSuccessfully":
|
||||
MessageLookupByLibrary.simpleMessage("Пароль паспяхова зменены"),
|
||||
@@ -231,8 +220,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"saveKey": MessageLookupByLibrary.simpleMessage("Захаваць ключ"),
|
||||
"scanCode": MessageLookupByLibrary.simpleMessage("Сканіраваць код"),
|
||||
"security": MessageLookupByLibrary.simpleMessage("Бяспека"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Абраць усё"),
|
||||
"selectReason":
|
||||
MessageLookupByLibrary.simpleMessage("Выберыце прычыну"),
|
||||
@@ -263,9 +250,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("Перарваць сеанс?"),
|
||||
"termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Умовы"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Тема"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("Гэта прылада"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
|
||||
19
mobile/lib/generated/intl/messages_bg.dart
generated
@@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'bg';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired.")
|
||||
};
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
||||
|
||||
19
mobile/lib/generated/intl/messages_ca.dart
generated
@@ -21,22 +21,5 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ca';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired.")
|
||||
};
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
|
||||
}
|
||||
|
||||
18
mobile/lib/generated/intl/messages_cs.dart
generated
@@ -22,26 +22,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Jaký je váš hlavní důvod, proč mažete svůj účet?"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
|
||||
"Zkontrolujte prosím svou doručenou poštu (a spam) pro dokončení ověření"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(""),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired.")
|
||||
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage("")
|
||||
};
|
||||
}
|
||||
|
||||
16
mobile/lib/generated/intl/messages_da.dart
generated
@@ -36,14 +36,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Aktive sessioner"),
|
||||
"addOnPageSubtitle":
|
||||
MessageLookupByLibrary.simpleMessage("Oplysninger om tilføjelser"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"askDeleteReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Hvad er hovedårsagen til, at du sletter din konto?"),
|
||||
"backedUpFolders":
|
||||
MessageLookupByLibrary.simpleMessage("Sikkerhedskopierede mapper"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Annuller"),
|
||||
"confirmAccountDeletion":
|
||||
MessageLookupByLibrary.simpleMessage("Bekræft Sletning Af Konto"),
|
||||
@@ -80,8 +76,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Indtast venligst en gyldig email adresse."),
|
||||
"enterYourEmailAddress":
|
||||
MessageLookupByLibrary.simpleMessage("Indtast din email adresse"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"familyPlanPortalTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Familie"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Feedback"),
|
||||
@@ -112,11 +106,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"next": MessageLookupByLibrary.simpleMessage("Næste"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("Ok"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Ups"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Adgangskode"),
|
||||
"pleaseContactSupportAndWeWillBeHappyToHelp":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
@@ -127,8 +116,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Skan denne QR-kode med godkendelses-appen"),
|
||||
"searchHint1":
|
||||
MessageLookupByLibrary.simpleMessage("Hurtig, søgning på enheden"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"selectReason": MessageLookupByLibrary.simpleMessage("Vælg årsag"),
|
||||
"selectedPhotos": m4,
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("Send email"),
|
||||
@@ -139,9 +126,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("Abonner"),
|
||||
"terminateSession":
|
||||
MessageLookupByLibrary.simpleMessage("Afslut session?"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Dette vil logge dig ud af følgende enhed:"),
|
||||
|
||||
175
mobile/lib/generated/intl/messages_de.dart
generated
@@ -142,105 +142,108 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m46(providerName) =>
|
||||
"Bitte kontaktiere den Support von ${providerName}, falls etwas abgebucht wurde";
|
||||
|
||||
static String m47(endDate) =>
|
||||
static String m47(count) =>
|
||||
"${Intl.plural(count, zero: '0 Fotos', one: '1 Foto', other: '${count} Fotos')}";
|
||||
|
||||
static String m48(endDate) =>
|
||||
"Kostenlose Testversion gültig bis ${endDate}.\nDu kannst anschließend ein bezahltes Paket auswählen.";
|
||||
|
||||
static String m48(toEmail) => "Bitte sende uns eine E-Mail an ${toEmail}";
|
||||
static String m49(toEmail) => "Bitte sende uns eine E-Mail an ${toEmail}";
|
||||
|
||||
static String m49(toEmail) => "Bitte sende die Protokolle an ${toEmail}";
|
||||
static String m50(toEmail) => "Bitte sende die Protokolle an ${toEmail}";
|
||||
|
||||
static String m50(folderName) => "Verarbeite ${folderName}...";
|
||||
static String m51(folderName) => "Verarbeite ${folderName}...";
|
||||
|
||||
static String m51(storeName) => "Bewerte uns auf ${storeName}";
|
||||
static String m52(storeName) => "Bewerte uns auf ${storeName}";
|
||||
|
||||
static String m52(storageInGB) =>
|
||||
static String m53(storageInGB) =>
|
||||
"3. Ihr beide erhaltet ${storageInGB} GB* kostenlos";
|
||||
|
||||
static String m53(userEmail) =>
|
||||
static String m54(userEmail) =>
|
||||
"${userEmail} wird aus diesem geteilten Album entfernt\n\nAlle von ihnen hinzugefügte Fotos werden ebenfalls aus dem Album entfernt";
|
||||
|
||||
static String m54(endDate) => "Erneuert am ${endDate}";
|
||||
static String m55(endDate) => "Erneuert am ${endDate}";
|
||||
|
||||
static String m55(count) =>
|
||||
static String m56(count) =>
|
||||
"${Intl.plural(count, one: '${count} Ergebnis gefunden', other: '${count} Ergebnisse gefunden')}";
|
||||
|
||||
static String m56(snapshotLenght, searchLenght) =>
|
||||
static String m57(snapshotLenght, searchLenght) =>
|
||||
"Abschnittslänge stimmt nicht überein: ${snapshotLenght} != ${searchLenght}";
|
||||
|
||||
static String m4(count) => "${count} ausgewählt";
|
||||
|
||||
static String m57(count, yourCount) =>
|
||||
static String m58(count, yourCount) =>
|
||||
"${count} ausgewählt (${yourCount} von Ihnen)";
|
||||
|
||||
static String m58(verificationID) =>
|
||||
static String m59(verificationID) =>
|
||||
"Hier ist meine Verifizierungs-ID: ${verificationID} für ente.io.";
|
||||
|
||||
static String m5(verificationID) =>
|
||||
"Hey, kannst du bestätigen, dass dies deine ente.io Verifizierungs-ID ist: ${verificationID}";
|
||||
|
||||
static String m59(referralCode, referralStorageInGB) =>
|
||||
static String m60(referralCode, referralStorageInGB) =>
|
||||
"Ente Weiterempfehlungs-Code: ${referralCode} \n\nEinlösen unter Einstellungen → Allgemein → Weiterempfehlungen, um ${referralStorageInGB} GB kostenlos zu erhalten, sobald Sie einen kostenpflichtigen Tarif abgeschlossen haben\n\nhttps://ente.io";
|
||||
|
||||
static String m60(numberOfPeople) =>
|
||||
static String m61(numberOfPeople) =>
|
||||
"${Intl.plural(numberOfPeople, zero: 'Teile mit bestimmten Personen', one: 'Teilen mit 1 Person', other: 'Teilen mit ${numberOfPeople} Personen')}";
|
||||
|
||||
static String m61(emailIDs) => "Geteilt mit ${emailIDs}";
|
||||
|
||||
static String m62(fileType) =>
|
||||
"Dieses ${fileType} wird von deinem Gerät gelöscht.";
|
||||
static String m62(emailIDs) => "Geteilt mit ${emailIDs}";
|
||||
|
||||
static String m63(fileType) =>
|
||||
"Dieses ${fileType} wird von deinem Gerät gelöscht.";
|
||||
|
||||
static String m64(fileType) =>
|
||||
"Diese Datei ist sowohl in Ente als auch auf deinem Gerät.";
|
||||
|
||||
static String m64(fileType) => "Diese Datei wird von Ente gelöscht.";
|
||||
static String m65(fileType) => "Diese Datei wird von Ente gelöscht.";
|
||||
|
||||
static String m1(storageAmountInGB) => "${storageAmountInGB} GB";
|
||||
|
||||
static String m65(
|
||||
static String m66(
|
||||
usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) =>
|
||||
"${usedAmount} ${usedStorageUnit} von ${totalAmount} ${totalStorageUnit} verwendet";
|
||||
|
||||
static String m66(id) =>
|
||||
static String m67(id) =>
|
||||
"Dein ${id} ist bereits mit einem anderen Ente-Konto verknüpft.\nWenn du deine ${id} mit diesem Konto verwenden möchtest, kontaktiere bitte unseren Support";
|
||||
|
||||
static String m67(endDate) => "Dein Abo endet am ${endDate}";
|
||||
static String m68(endDate) => "Dein Abo endet am ${endDate}";
|
||||
|
||||
static String m68(completed, total) =>
|
||||
static String m69(completed, total) =>
|
||||
"${completed}/${total} Erinnerungsstücke gesichert";
|
||||
|
||||
static String m69(ignoreReason) =>
|
||||
static String m70(ignoreReason) =>
|
||||
"Zum Hochladen tippen, Hochladen wird derzeit ignoriert, da ${ignoreReason}";
|
||||
|
||||
static String m70(storageAmountInGB) =>
|
||||
static String m71(storageAmountInGB) =>
|
||||
"Diese erhalten auch ${storageAmountInGB} GB";
|
||||
|
||||
static String m71(email) => "Dies ist ${email}s Verifizierungs-ID";
|
||||
static String m72(email) => "Dies ist ${email}s Verifizierungs-ID";
|
||||
|
||||
static String m72(count) =>
|
||||
"${Intl.plural(count, zero: '', one: '1 Tag', other: '${count} Tage')}";
|
||||
static String m73(count) =>
|
||||
"${Intl.plural(count, zero: 'Demnächst', one: '1 Tag', other: '${count} Tage')}";
|
||||
|
||||
static String m73(galleryType) =>
|
||||
static String m74(galleryType) =>
|
||||
"Der Galerie-Typ ${galleryType} unterstützt kein Umbenennen";
|
||||
|
||||
static String m74(ignoreReason) =>
|
||||
static String m75(ignoreReason) =>
|
||||
"Upload wird aufgrund von ${ignoreReason} ignoriert";
|
||||
|
||||
static String m75(count) => "Sichere ${count} Erinnerungsstücke...";
|
||||
static String m76(count) => "Sichere ${count} Erinnerungsstücke...";
|
||||
|
||||
static String m76(endDate) => "Gültig bis ${endDate}";
|
||||
static String m77(endDate) => "Gültig bis ${endDate}";
|
||||
|
||||
static String m77(email) => "Verifiziere ${email}";
|
||||
static String m78(email) => "Verifiziere ${email}";
|
||||
|
||||
static String m78(count) =>
|
||||
static String m79(count) =>
|
||||
"${Intl.plural(count, zero: '0 Betrachter hinzugefügt', one: '1 Betrachter hinzugefügt', other: '${count} Betrachter hinzugefügt')}";
|
||||
|
||||
static String m2(email) =>
|
||||
"Wir haben eine E-Mail an <green>${email}</green> gesendet";
|
||||
|
||||
static String m79(count) =>
|
||||
static String m80(count) =>
|
||||
"${Intl.plural(count, one: 'vor einem Jahr', other: 'vor ${count} Jahren')}";
|
||||
|
||||
static String m80(storageSaved) =>
|
||||
static String m81(storageSaved) =>
|
||||
"Du hast ${storageSaved} erfolgreich freigegeben!";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
@@ -318,13 +321,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Alle Erinnerungsstücke gesichert"),
|
||||
"allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage(
|
||||
"Alle Gruppierungen für diese Person werden zurückgesetzt und du wirst alle Vorschläge für diese Person verlieren"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Erlauben"),
|
||||
"allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Erlaube Nutzern, mit diesem Link ebenfalls Fotos zu diesem geteilten Album hinzuzufügen."),
|
||||
"allowAddingPhotos": MessageLookupByLibrary.simpleMessage(
|
||||
"Hinzufügen von Fotos erlauben"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"Erlaube der App, geteilte Album-Links zu öffnen"),
|
||||
"allowDownloads":
|
||||
MessageLookupByLibrary.simpleMessage("Downloads erlauben"),
|
||||
"allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -440,7 +443,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Backup"),
|
||||
"backupFailed":
|
||||
MessageLookupByLibrary.simpleMessage("Sicherung fehlgeschlagen"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Datei sichern"),
|
||||
"backupOverMobileData":
|
||||
MessageLookupByLibrary.simpleMessage("Über mobile Daten sichern"),
|
||||
"backupSettings":
|
||||
@@ -571,7 +574,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"confirmAccountDeletion":
|
||||
MessageLookupByLibrary.simpleMessage("Kontolöschung bestätigen"),
|
||||
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps endgültig und unwiderruflich löschen."),
|
||||
"Ja, ich möchte dieses Konto und alle enthaltenen Daten über alle Apps hinweg endgültig löschen."),
|
||||
"confirmPassword":
|
||||
MessageLookupByLibrary.simpleMessage("Passwort wiederholen"),
|
||||
"confirmPlanChange":
|
||||
@@ -658,7 +661,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Dieses Konto ist mit anderen Ente-Apps verknüpft, falls du welche verwendest. Deine hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und dein Konto wird endgültig gelöscht."),
|
||||
"deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
|
||||
"Bitte sende eine E-Mail an <warning>account-deletion@ente.io</warning> von Deiner bei uns hinterlegten E-Mail-Adresse."),
|
||||
"Bitte sende eine E-Mail an <warning>account-deletion@ente.io</warning> von Ihrer bei uns hinterlegten E-Mail-Adresse."),
|
||||
"deleteEmptyAlbums":
|
||||
MessageLookupByLibrary.simpleMessage("Leere Alben löschen"),
|
||||
"deleteEmptyAlbumsWithQuestionMark":
|
||||
@@ -800,7 +803,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"enteCanEncryptAndPreserveFilesOnlyIfYouGrant":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Ente kann Dateien nur verschlüsseln und sichern, wenn du den Zugriff darauf gewährst"),
|
||||
"entePhotosPerm": MessageLookupByLibrary.simpleMessage(""),
|
||||
"entePhotosPerm": MessageLookupByLibrary.simpleMessage(
|
||||
"Ente <i> benötigt Berechtigung, um <i> Ihre Fotos zu sichern"),
|
||||
"enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage(
|
||||
"Ente sichert deine Erinnerungen, sodass sie dir nie verloren gehen, selbst wenn du dein Gerät verlierst."),
|
||||
"enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -852,6 +856,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"extraPhotosFound":
|
||||
MessageLookupByLibrary.simpleMessage("Zusätzliche Fotos gefunden"),
|
||||
"extraPhotosFoundFor": m30,
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Gesicht ist noch nicht gruppiert, bitte komm später zurück"),
|
||||
"faceRecognition":
|
||||
MessageLookupByLibrary.simpleMessage("Gesichtserkennung"),
|
||||
"faces": MessageLookupByLibrary.simpleMessage("Gesichter"),
|
||||
@@ -1106,6 +1112,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Deine Sitzung ist abgelaufen. Bitte melde Dich erneut an."),
|
||||
"loginTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"Mit dem Klick auf \"Anmelden\" stimme ich den <u-terms>Nutzungsbedingungen</u-terms> und der <u-policy>Datenschutzerklärung</u-policy> zu"),
|
||||
"loginWithTOTP":
|
||||
MessageLookupByLibrary.simpleMessage("Mit TOTP anmelden"),
|
||||
"logout": MessageLookupByLibrary.simpleMessage("Ausloggen"),
|
||||
"logsDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Dies wird über Logs gesendet, um uns zu helfen, Ihr Problem zu beheben. Bitte beachten Sie, dass Dateinamen aufgenommen werden, um Probleme mit bestimmten Dateien zu beheben."),
|
||||
@@ -1125,7 +1133,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Die magische Suche erlaubt das Durchsuchen von Fotos nach ihrem Inhalt, z.B. \'Blumen\', \'rotes Auto\', \'Ausweisdokumente\'"),
|
||||
"manage": MessageLookupByLibrary.simpleMessage("Verwalten"),
|
||||
"manageDeviceStorage":
|
||||
MessageLookupByLibrary.simpleMessage("Gerätespeicher verwalten"),
|
||||
MessageLookupByLibrary.simpleMessage("Geräte-Cache verwalten"),
|
||||
"manageDeviceStorageDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Lokalen Cache-Speicher überprüfen und löschen."),
|
||||
"manageFamily":
|
||||
MessageLookupByLibrary.simpleMessage("Familiengruppe verwalten"),
|
||||
"manageLink": MessageLookupByLibrary.simpleMessage("Link verwalten"),
|
||||
@@ -1246,10 +1256,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage(
|
||||
"Ups. Leider ist ein Fehler aufgetreten"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
MessageLookupByLibrary.simpleMessage("Album im Browser öffnen"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"Bitte nutze die Web-App, um Fotos zu diesem Album hinzuzufügen"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Datei öffnen"),
|
||||
"openSettings":
|
||||
MessageLookupByLibrary.simpleMessage("Öffne Einstellungen"),
|
||||
"openTheItem": MessageLookupByLibrary.simpleMessage("• Element öffnen"),
|
||||
@@ -1310,13 +1320,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"photosAddedByYouWillBeRemovedFromTheAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Von dir hinzugefügte Fotos werden vom Album entfernt"),
|
||||
"photosCount": m47,
|
||||
"pickCenterPoint":
|
||||
MessageLookupByLibrary.simpleMessage("Mittelpunkt auswählen"),
|
||||
"pinAlbum": MessageLookupByLibrary.simpleMessage("Album anheften"),
|
||||
"pinLock": MessageLookupByLibrary.simpleMessage("PIN-Sperre"),
|
||||
"playOnTv": MessageLookupByLibrary.simpleMessage(
|
||||
"Album auf dem Fernseher wiedergeben"),
|
||||
"playStoreFreeTrialValidTill": m47,
|
||||
"playStoreFreeTrialValidTill": m48,
|
||||
"playstoreSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("PlayStore Abo"),
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain":
|
||||
@@ -1328,14 +1339,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"pleaseContactSupportIfTheProblemPersists":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Bitte wenden Sie sich an den Support, falls das Problem weiterhin besteht"),
|
||||
"pleaseEmailUsAt": m48,
|
||||
"pleaseEmailUsAt": m49,
|
||||
"pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage(
|
||||
"Bitte erteile die nötigen Berechtigungen"),
|
||||
"pleaseLoginAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Bitte logge dich erneut ein"),
|
||||
"pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage(
|
||||
"Bitte wähle die zu entfernenden schnellen Links"),
|
||||
"pleaseSendTheLogsTo": m49,
|
||||
"pleaseSendTheLogsTo": m50,
|
||||
"pleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Bitte versuche es erneut"),
|
||||
"pleaseVerifyTheCodeYouHaveEntered":
|
||||
@@ -1362,7 +1373,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Private Sicherungen"),
|
||||
"privateSharing":
|
||||
MessageLookupByLibrary.simpleMessage("Privates Teilen"),
|
||||
"processingImport": m50,
|
||||
"processingImport": m51,
|
||||
"publicLinkCreated":
|
||||
MessageLookupByLibrary.simpleMessage("Öffentlicher Link erstellt"),
|
||||
"publicLinkEnabled":
|
||||
@@ -1372,7 +1383,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"raiseTicket": MessageLookupByLibrary.simpleMessage("Ticket erstellen"),
|
||||
"rateTheApp": MessageLookupByLibrary.simpleMessage("App bewerten"),
|
||||
"rateUs": MessageLookupByLibrary.simpleMessage("Bewerte uns"),
|
||||
"rateUsOnStore": m51,
|
||||
"rateUsOnStore": m52,
|
||||
"recover": MessageLookupByLibrary.simpleMessage("Wiederherstellen"),
|
||||
"recoverAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Konto wiederherstellen"),
|
||||
@@ -1409,7 +1420,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"1. Gib diesen Code an deine Freunde"),
|
||||
"referralStep2": MessageLookupByLibrary.simpleMessage(
|
||||
"2. Sie schließen ein bezahltes Abo ab"),
|
||||
"referralStep3": m52,
|
||||
"referralStep3": m53,
|
||||
"referrals": MessageLookupByLibrary.simpleMessage("Weiterempfehlungen"),
|
||||
"referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage(
|
||||
"Einlösungen sind derzeit pausiert"),
|
||||
@@ -1437,7 +1448,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"removeLink": MessageLookupByLibrary.simpleMessage("Link entfernen"),
|
||||
"removeParticipant":
|
||||
MessageLookupByLibrary.simpleMessage("Teilnehmer entfernen"),
|
||||
"removeParticipantBody": m53,
|
||||
"removeParticipantBody": m54,
|
||||
"removePersonLabel":
|
||||
MessageLookupByLibrary.simpleMessage("Personenetikett entfernen"),
|
||||
"removePublicLink":
|
||||
@@ -1455,7 +1466,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"renameFile": MessageLookupByLibrary.simpleMessage("Datei umbenennen"),
|
||||
"renewSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Abonnement erneuern"),
|
||||
"renewsOn": m54,
|
||||
"renewsOn": m55,
|
||||
"reportABug": MessageLookupByLibrary.simpleMessage("Fehler melden"),
|
||||
"reportBug": MessageLookupByLibrary.simpleMessage("Fehler melden"),
|
||||
"resendEmail":
|
||||
@@ -1534,11 +1545,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Laden Sie Personen ein, damit Sie geteilte Fotos hier einsehen können"),
|
||||
"searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage(
|
||||
"Personen werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist"),
|
||||
"searchResultCount": m55,
|
||||
"searchSectionsLengthMismatch": m56,
|
||||
"searchResultCount": m56,
|
||||
"searchSectionsLengthMismatch": m57,
|
||||
"security": MessageLookupByLibrary.simpleMessage("Sicherheit"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"Öffentliche Album-Links in der App ansehen"),
|
||||
"selectALocation":
|
||||
MessageLookupByLibrary.simpleMessage("Standort auswählen"),
|
||||
"selectALocationFirst":
|
||||
@@ -1570,7 +1581,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Ausgewählte Elemente werden aus allen Alben gelöscht und in den Papierkorb verschoben."),
|
||||
"selectedPhotos": m4,
|
||||
"selectedPhotosWithYours": m57,
|
||||
"selectedPhotosWithYours": m58,
|
||||
"send": MessageLookupByLibrary.simpleMessage("Absenden"),
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("E-Mail senden"),
|
||||
"sendInvite": MessageLookupByLibrary.simpleMessage("Einladung senden"),
|
||||
@@ -1600,16 +1611,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"shareAnAlbumNow":
|
||||
MessageLookupByLibrary.simpleMessage("Teile jetzt ein Album"),
|
||||
"shareLink": MessageLookupByLibrary.simpleMessage("Link teilen"),
|
||||
"shareMyVerificationID": m58,
|
||||
"shareMyVerificationID": m59,
|
||||
"shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage(
|
||||
"Teile mit ausgewählten Personen"),
|
||||
"shareTextConfirmOthersVerificationID": m5,
|
||||
"shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage(
|
||||
"Hol dir Ente, damit wir ganz einfach Fotos und Videos in Originalqualität teilen können\n\nhttps://ente.io"),
|
||||
"shareTextReferralCode": m59,
|
||||
"shareTextReferralCode": m60,
|
||||
"shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage(
|
||||
"Mit Nicht-Ente-Benutzern teilen"),
|
||||
"shareWithPeopleSectionTitle": m60,
|
||||
"shareWithPeopleSectionTitle": m61,
|
||||
"shareYourFirstAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Teile dein erstes Album"),
|
||||
"sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1620,7 +1631,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Neue geteilte Fotos"),
|
||||
"sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage(
|
||||
"Erhalte Benachrichtigungen, wenn jemand ein Foto zu einem gemeinsam genutzten Album hinzufügt, dem du angehörst"),
|
||||
"sharedWith": m61,
|
||||
"sharedWith": m62,
|
||||
"sharedWithMe": MessageLookupByLibrary.simpleMessage("Mit mir geteilt"),
|
||||
"sharedWithYou":
|
||||
MessageLookupByLibrary.simpleMessage("Mit dir geteilt"),
|
||||
@@ -1636,11 +1647,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Andere Geräte abmelden"),
|
||||
"signUpTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"Ich stimme den <u-terms>Nutzungsbedingungen</u-terms> und der <u-policy>Datenschutzerklärung</u-policy> zu"),
|
||||
"singleFileDeleteFromDevice": m62,
|
||||
"singleFileDeleteFromDevice": m63,
|
||||
"singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage(
|
||||
"Es wird aus allen Alben gelöscht."),
|
||||
"singleFileInBothLocalAndRemote": m63,
|
||||
"singleFileInRemoteOnly": m64,
|
||||
"singleFileInBothLocalAndRemote": m64,
|
||||
"singleFileInRemoteOnly": m65,
|
||||
"skip": MessageLookupByLibrary.simpleMessage("Überspringen"),
|
||||
"social": MessageLookupByLibrary.simpleMessage("Social Media"),
|
||||
"someItemsAreInBothEnteAndYourDevice":
|
||||
@@ -1690,10 +1701,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"storageInGB": m1,
|
||||
"storageLimitExceeded": MessageLookupByLibrary.simpleMessage(
|
||||
"Speichergrenze überschritten"),
|
||||
"storageUsageInfo": m65,
|
||||
"storageUsageInfo": m66,
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Stark"),
|
||||
"subAlreadyLinkedErrMessage": m66,
|
||||
"subWillBeCancelledOn": m67,
|
||||
"subAlreadyLinkedErrMessage": m67,
|
||||
"subWillBeCancelledOn": m68,
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("Abonnieren"),
|
||||
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
|
||||
"Du benötigst ein aktives, bezahltes Abonnement, um das Teilen zu aktivieren."),
|
||||
@@ -1710,7 +1721,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"suggestFeatures":
|
||||
MessageLookupByLibrary.simpleMessage("Verbesserung vorschlagen"),
|
||||
"support": MessageLookupByLibrary.simpleMessage("Support"),
|
||||
"syncProgress": m68,
|
||||
"syncProgress": m69,
|
||||
"syncStopped":
|
||||
MessageLookupByLibrary.simpleMessage("Synchronisierung angehalten"),
|
||||
"syncing": MessageLookupByLibrary.simpleMessage("Synchronisiere …"),
|
||||
@@ -1723,7 +1734,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Zum Entsperren antippen"),
|
||||
"tapToUpload":
|
||||
MessageLookupByLibrary.simpleMessage("Zum Hochladen antippen"),
|
||||
"tapToUploadIsIgnoredDue": m69,
|
||||
"tapToUploadIsIgnoredDue": m70,
|
||||
"tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage(
|
||||
"Etwas ist schiefgelaufen. Bitte versuche es später noch einmal. Sollte der Fehler weiter bestehen, kontaktiere unser Supportteam."),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Beenden"),
|
||||
@@ -1739,7 +1750,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Der Download konnte nicht abgeschlossen werden"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"Der Link, den du aufrufen möchtest, ist abgelaufen."),
|
||||
"theRecoveryKeyYouEnteredIsIncorrect":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Der eingegebene Schlüssel ist ungültig"),
|
||||
@@ -1747,7 +1758,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"theseItemsWillBeDeletedFromYourDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Diese Elemente werden von deinem Gerät gelöscht."),
|
||||
"theyAlsoGetXGb": m70,
|
||||
"theyAlsoGetXGb": m71,
|
||||
"theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage(
|
||||
"Sie werden aus allen Alben gelöscht."),
|
||||
"thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1763,7 +1774,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Diese E-Mail-Adresse wird bereits verwendet"),
|
||||
"thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage(
|
||||
"Dieses Bild hat keine Exif-Daten"),
|
||||
"thisIsPersonVerificationId": m71,
|
||||
"thisIsPersonVerificationId": m72,
|
||||
"thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage(
|
||||
"Dies ist deine Verifizierungs-ID"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
@@ -1788,7 +1799,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"total": MessageLookupByLibrary.simpleMessage("Gesamt"),
|
||||
"totalSize": MessageLookupByLibrary.simpleMessage("Gesamtgröße"),
|
||||
"trash": MessageLookupByLibrary.simpleMessage("Papierkorb"),
|
||||
"trashDaysLeft": m72,
|
||||
"trashDaysLeft": m73,
|
||||
"trim": MessageLookupByLibrary.simpleMessage("Schneiden"),
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Erneut versuchen"),
|
||||
"turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1808,7 +1819,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Zwei-Faktor-Authentifizierung (2FA) erfolgreich zurückgesetzt"),
|
||||
"twofactorSetup": MessageLookupByLibrary.simpleMessage(
|
||||
"Zweiten Faktor (2FA) einrichten"),
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m73,
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m74,
|
||||
"unarchive": MessageLookupByLibrary.simpleMessage("Dearchivieren"),
|
||||
"unarchiveAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Album dearchivieren"),
|
||||
@@ -1832,10 +1843,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"updatingFolderSelection": MessageLookupByLibrary.simpleMessage(
|
||||
"Ordnerauswahl wird aktualisiert..."),
|
||||
"upgrade": MessageLookupByLibrary.simpleMessage("Upgrade"),
|
||||
"uploadIsIgnoredDueToIgnorereason": m74,
|
||||
"uploadIsIgnoredDueToIgnorereason": m75,
|
||||
"uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
|
||||
"Dateien werden ins Album hochgeladen..."),
|
||||
"uploadingMultipleMemories": m75,
|
||||
"uploadingMultipleMemories": m76,
|
||||
"uploadingSingleMemory": MessageLookupByLibrary.simpleMessage(
|
||||
"Sichere ein Erinnerungsstück..."),
|
||||
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1852,7 +1863,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Ausgewähltes Foto verwenden"),
|
||||
"usedSpace":
|
||||
MessageLookupByLibrary.simpleMessage("Belegter Speicherplatz"),
|
||||
"validTill": m76,
|
||||
"validTill": m77,
|
||||
"verificationFailedPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Verifizierung fehlgeschlagen, bitte versuchen Sie es erneut"),
|
||||
@@ -1861,7 +1872,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Überprüfen"),
|
||||
"verifyEmail":
|
||||
MessageLookupByLibrary.simpleMessage("E-Mail-Adresse verifizieren"),
|
||||
"verifyEmailID": m77,
|
||||
"verifyEmailID": m78,
|
||||
"verifyIDLabel": MessageLookupByLibrary.simpleMessage("Überprüfen"),
|
||||
"verifyPasskey":
|
||||
MessageLookupByLibrary.simpleMessage("Passkey verifizieren"),
|
||||
@@ -1888,7 +1899,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"viewRecoveryKey": MessageLookupByLibrary.simpleMessage(
|
||||
"Wiederherstellungsschlüssel anzeigen"),
|
||||
"viewer": MessageLookupByLibrary.simpleMessage("Zuschauer"),
|
||||
"viewersSuccessfullyAdded": m78,
|
||||
"viewersSuccessfullyAdded": m79,
|
||||
"visitWebToManage": MessageLookupByLibrary.simpleMessage(
|
||||
"Bitte rufe \"web.ente.io\" auf, um dein Abo zu verwalten"),
|
||||
"waitingForVerification":
|
||||
@@ -1907,7 +1918,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"whatsNew": MessageLookupByLibrary.simpleMessage("Neue Funktionen"),
|
||||
"yearShort": MessageLookupByLibrary.simpleMessage("Jahr"),
|
||||
"yearly": MessageLookupByLibrary.simpleMessage("Jährlich"),
|
||||
"yearsAgo": m79,
|
||||
"yearsAgo": m80,
|
||||
"yes": MessageLookupByLibrary.simpleMessage("Ja"),
|
||||
"yesCancel": MessageLookupByLibrary.simpleMessage("Ja, kündigen"),
|
||||
"yesConvertToViewer": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1939,7 +1950,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Du kannst nicht mit dir selbst teilen"),
|
||||
"youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage(
|
||||
"Du hast keine archivierten Elemente."),
|
||||
"youHaveSuccessfullyFreedUp": m80,
|
||||
"youHaveSuccessfullyFreedUp": m81,
|
||||
"yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage(
|
||||
"Dein Benutzerkonto wurde gelöscht"),
|
||||
"yourMap": MessageLookupByLibrary.simpleMessage("Deine Karte"),
|
||||
|
||||
18
mobile/lib/generated/intl/messages_el.dart
generated
@@ -22,23 +22,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"enterYourEmailAddress": MessageLookupByLibrary.simpleMessage(
|
||||
"Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired.")
|
||||
"Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας")
|
||||
};
|
||||
}
|
||||
|
||||
209
mobile/lib/generated/intl/messages_en.dart
generated
@@ -59,9 +59,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
static String m18(albumName) => "Collaborative link created for ${albumName}";
|
||||
|
||||
static String m81(count) =>
|
||||
static String m82(count) =>
|
||||
"${Intl.plural(count, zero: 'Added 0 collaborator', one: 'Added 1 collaborator', other: 'Added ${count} collaborators')}";
|
||||
|
||||
static String m83(email, numOfDays) =>
|
||||
"You are about to add ${email} as a trusted contact. They will be able to recover your account if you are absent for ${numOfDays} days.";
|
||||
|
||||
static String m19(familyAdminEmail) =>
|
||||
"Please contact <green>${familyAdminEmail}</green> to manage your subscription";
|
||||
|
||||
@@ -120,6 +123,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m39(count) =>
|
||||
"${Intl.plural(count, one: '${count} item', other: '${count} items')}";
|
||||
|
||||
static String m84(email) =>
|
||||
"${email} has invited you to be a trusted contact";
|
||||
|
||||
static String m40(expiryTime) => "Link will expire on ${expiryTime}";
|
||||
|
||||
static String m3(count, formattedCount) =>
|
||||
@@ -143,107 +149,118 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m46(providerName) =>
|
||||
"Please talk to ${providerName} support if you were charged";
|
||||
|
||||
static String m82(count) =>
|
||||
static String m47(count) =>
|
||||
"${Intl.plural(count, zero: '0 photo', one: '1 photo', other: '${count} photos')}";
|
||||
|
||||
static String m47(endDate) =>
|
||||
static String m48(endDate) =>
|
||||
"Free trial valid till ${endDate}.\nYou can choose a paid plan afterwards.";
|
||||
|
||||
static String m48(toEmail) => "Please email us at ${toEmail}";
|
||||
static String m49(toEmail) => "Please email us at ${toEmail}";
|
||||
|
||||
static String m49(toEmail) => "Please send the logs to \n${toEmail}";
|
||||
static String m50(toEmail) => "Please send the logs to \n${toEmail}";
|
||||
|
||||
static String m50(folderName) => "Processing ${folderName}...";
|
||||
static String m51(folderName) => "Processing ${folderName}...";
|
||||
|
||||
static String m51(storeName) => "Rate us on ${storeName}";
|
||||
static String m52(storeName) => "Rate us on ${storeName}";
|
||||
|
||||
static String m52(storageInGB) =>
|
||||
static String m85(days, email) =>
|
||||
"You can access the account after ${days} days. A notification will be sent to ${email}.";
|
||||
|
||||
static String m86(email) =>
|
||||
"You can now recover ${email}\'s account by setting a new password.";
|
||||
|
||||
static String m87(email) => "${email} is trying to recover your account.";
|
||||
|
||||
static String m53(storageInGB) =>
|
||||
"3. Both of you get ${storageInGB} GB* free";
|
||||
|
||||
static String m53(userEmail) =>
|
||||
static String m54(userEmail) =>
|
||||
"${userEmail} will be removed from this shared album\n\nAny photos added by them will also be removed from the album";
|
||||
|
||||
static String m54(endDate) => "Subscription renews on ${endDate}";
|
||||
static String m55(endDate) => "Subscription renews on ${endDate}";
|
||||
|
||||
static String m55(count) =>
|
||||
static String m56(count) =>
|
||||
"${Intl.plural(count, one: '${count} result found', other: '${count} results found')}";
|
||||
|
||||
static String m56(snapshotLenght, searchLenght) =>
|
||||
static String m57(snapshotLenght, searchLenght) =>
|
||||
"Sections length mismatch: ${snapshotLenght} != ${searchLenght}";
|
||||
|
||||
static String m4(count) => "${count} selected";
|
||||
|
||||
static String m57(count, yourCount) =>
|
||||
static String m58(count, yourCount) =>
|
||||
"${count} selected (${yourCount} yours)";
|
||||
|
||||
static String m58(verificationID) =>
|
||||
static String m59(verificationID) =>
|
||||
"Here\'s my verification ID: ${verificationID} for ente.io.";
|
||||
|
||||
static String m5(verificationID) =>
|
||||
"Hey, can you confirm that this is your ente.io verification ID: ${verificationID}";
|
||||
|
||||
static String m59(referralCode, referralStorageInGB) =>
|
||||
static String m60(referralCode, referralStorageInGB) =>
|
||||
"Ente referral code: ${referralCode} \n\nApply it in Settings → General → Referrals to get ${referralStorageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io";
|
||||
|
||||
static String m60(numberOfPeople) =>
|
||||
static String m61(numberOfPeople) =>
|
||||
"${Intl.plural(numberOfPeople, zero: 'Share with specific people', one: 'Shared with 1 person', other: 'Shared with ${numberOfPeople} people')}";
|
||||
|
||||
static String m61(emailIDs) => "Shared with ${emailIDs}";
|
||||
|
||||
static String m62(fileType) =>
|
||||
"This ${fileType} will be deleted from your device.";
|
||||
static String m62(emailIDs) => "Shared with ${emailIDs}";
|
||||
|
||||
static String m63(fileType) =>
|
||||
"This ${fileType} will be deleted from your device.";
|
||||
|
||||
static String m64(fileType) =>
|
||||
"This ${fileType} is in both Ente and your device.";
|
||||
|
||||
static String m64(fileType) => "This ${fileType} will be deleted from Ente.";
|
||||
static String m65(fileType) => "This ${fileType} will be deleted from Ente.";
|
||||
|
||||
static String m1(storageAmountInGB) => "${storageAmountInGB} GB";
|
||||
|
||||
static String m65(
|
||||
static String m66(
|
||||
usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) =>
|
||||
"${usedAmount} ${usedStorageUnit} of ${totalAmount} ${totalStorageUnit} used";
|
||||
|
||||
static String m66(id) =>
|
||||
static String m67(id) =>
|
||||
"Your ${id} is already linked to another Ente account.\nIf you would like to use your ${id} with this account, please contact our support\'\'";
|
||||
|
||||
static String m67(endDate) =>
|
||||
static String m68(endDate) =>
|
||||
"Your subscription will be cancelled on ${endDate}";
|
||||
|
||||
static String m68(completed, total) =>
|
||||
static String m69(completed, total) =>
|
||||
"${completed}/${total} memories preserved";
|
||||
|
||||
static String m69(ignoreReason) =>
|
||||
static String m70(ignoreReason) =>
|
||||
"Tap to upload, upload is currently ignored due to ${ignoreReason}";
|
||||
|
||||
static String m70(storageAmountInGB) =>
|
||||
static String m71(storageAmountInGB) =>
|
||||
"They also get ${storageAmountInGB} GB";
|
||||
|
||||
static String m71(email) => "This is ${email}\'s Verification ID";
|
||||
static String m72(email) => "This is ${email}\'s Verification ID";
|
||||
|
||||
static String m72(count) =>
|
||||
static String m73(count) =>
|
||||
"${Intl.plural(count, zero: 'Soon', one: '1 day', other: '${count} days')}";
|
||||
|
||||
static String m73(galleryType) =>
|
||||
static String m88(email) =>
|
||||
"You have been invited to be a legacy contact by ${email}.";
|
||||
|
||||
static String m74(galleryType) =>
|
||||
"Type of gallery ${galleryType} is not supported for rename";
|
||||
|
||||
static String m74(ignoreReason) => "Upload is ignored due to ${ignoreReason}";
|
||||
static String m75(ignoreReason) => "Upload is ignored due to ${ignoreReason}";
|
||||
|
||||
static String m75(count) => "Preserving ${count} memories...";
|
||||
static String m76(count) => "Preserving ${count} memories...";
|
||||
|
||||
static String m76(endDate) => "Valid till ${endDate}";
|
||||
static String m77(endDate) => "Valid till ${endDate}";
|
||||
|
||||
static String m77(email) => "Verify ${email}";
|
||||
static String m78(email) => "Verify ${email}";
|
||||
|
||||
static String m78(count) =>
|
||||
static String m79(count) =>
|
||||
"${Intl.plural(count, zero: 'Added 0 viewer', one: 'Added 1 viewer', other: 'Added ${count} viewers')}";
|
||||
|
||||
static String m2(email) => "We have sent a mail to <green>${email}</green>";
|
||||
|
||||
static String m79(count) =>
|
||||
static String m80(count) =>
|
||||
"${Intl.plural(count, one: '${count} year ago', other: '${count} years ago')}";
|
||||
|
||||
static String m80(storageSaved) =>
|
||||
static String m81(storageSaved) =>
|
||||
"You have successfully freed up ${storageSaved}!";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
@@ -251,6 +268,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage(
|
||||
"A new version of Ente is available."),
|
||||
"about": MessageLookupByLibrary.simpleMessage("About"),
|
||||
"acceptTrustInvite":
|
||||
MessageLookupByLibrary.simpleMessage("Accept Invite"),
|
||||
"account": MessageLookupByLibrary.simpleMessage("Account"),
|
||||
"accountIsAlreadyConfigured": MessageLookupByLibrary.simpleMessage(
|
||||
"Account is already configured."),
|
||||
@@ -288,6 +307,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addToEnte": MessageLookupByLibrary.simpleMessage("Add to Ente"),
|
||||
"addToHiddenAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Add to hidden album"),
|
||||
"addTrustedContact":
|
||||
MessageLookupByLibrary.simpleMessage("Add Trusted Contact"),
|
||||
"addViewer": MessageLookupByLibrary.simpleMessage("Add viewer"),
|
||||
"addViewers": m9,
|
||||
"addYourPhotosNow":
|
||||
@@ -395,6 +416,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Please authenticate to configure two-factor authentication"),
|
||||
"authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage(
|
||||
"Please authenticate to initiate account deletion"),
|
||||
"authToManageLegacy": MessageLookupByLibrary.simpleMessage(
|
||||
"Please authenticate to manage your trusted contacts"),
|
||||
"authToViewPasskey": MessageLookupByLibrary.simpleMessage(
|
||||
"Please authenticate to view your passkey"),
|
||||
"authToViewYourActiveSessions": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -454,6 +477,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"canOnlyRemoveFilesOwnedByYou": MessageLookupByLibrary.simpleMessage(
|
||||
"Can only remove files owned by you"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Cancel"),
|
||||
"cancelAccountRecovery":
|
||||
MessageLookupByLibrary.simpleMessage("Cancel recovery"),
|
||||
"cancelAccountRecoveryBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Are you sure you want to cancel recovery?"),
|
||||
"cancelOtherSubscription": m15,
|
||||
"cancelSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Cancel subscription"),
|
||||
@@ -539,7 +566,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Collaborators can add photos and videos to the shared album."),
|
||||
"collaboratorsSuccessfullyAdded": m81,
|
||||
"collaboratorsSuccessfullyAdded": m82,
|
||||
"collageLayout": MessageLookupByLibrary.simpleMessage("Layout"),
|
||||
"collageSaved":
|
||||
MessageLookupByLibrary.simpleMessage("Collage saved to gallery"),
|
||||
@@ -556,6 +583,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Are you sure you want to disable two-factor authentication?"),
|
||||
"confirmAccountDeletion":
|
||||
MessageLookupByLibrary.simpleMessage("Confirm Account Deletion"),
|
||||
"confirmAddingTrustedContact": m83,
|
||||
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
|
||||
"Yes, I want to permanently delete this account and its data across all apps."),
|
||||
"confirmPassword":
|
||||
@@ -621,6 +649,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"darkTheme": MessageLookupByLibrary.simpleMessage("Dark"),
|
||||
"dayToday": MessageLookupByLibrary.simpleMessage("Today"),
|
||||
"dayYesterday": MessageLookupByLibrary.simpleMessage("Yesterday"),
|
||||
"declineTrustInvite":
|
||||
MessageLookupByLibrary.simpleMessage("Decline Invite"),
|
||||
"decrypting": MessageLookupByLibrary.simpleMessage("Decrypting..."),
|
||||
"decryptingVideo":
|
||||
MessageLookupByLibrary.simpleMessage("Decrypting video..."),
|
||||
@@ -756,6 +786,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Email verification"),
|
||||
"emailYourLogs":
|
||||
MessageLookupByLibrary.simpleMessage("Email your logs"),
|
||||
"emergencyContacts":
|
||||
MessageLookupByLibrary.simpleMessage("Emergency Contacts"),
|
||||
"empty": MessageLookupByLibrary.simpleMessage("Empty"),
|
||||
"emptyTrash": MessageLookupByLibrary.simpleMessage("Empty trash?"),
|
||||
"enable": MessageLookupByLibrary.simpleMessage("Enable"),
|
||||
@@ -1009,6 +1041,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"leaveSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Leave shared album?"),
|
||||
"left": MessageLookupByLibrary.simpleMessage("Left"),
|
||||
"legacy": MessageLookupByLibrary.simpleMessage("Legacy"),
|
||||
"legacyAccounts":
|
||||
MessageLookupByLibrary.simpleMessage("Legacy accounts"),
|
||||
"legacyInvite": m84,
|
||||
"legacyPageDesc": MessageLookupByLibrary.simpleMessage(
|
||||
"Legacy allows trusted contacts to access your account in your absence."),
|
||||
"legacyPageDesc2": MessageLookupByLibrary.simpleMessage(
|
||||
"Trusted contacts can initiate account recovery, and if not blocked within 30 days, reset your password and access your account."),
|
||||
"light": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"lightTheme": MessageLookupByLibrary.simpleMessage("Light"),
|
||||
"linkCopiedToClipboard":
|
||||
@@ -1266,13 +1306,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"photosAddedByYouWillBeRemovedFromTheAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Photos added by you will be removed from the album"),
|
||||
"photosCount": m82,
|
||||
"photosCount": m47,
|
||||
"pickCenterPoint":
|
||||
MessageLookupByLibrary.simpleMessage("Pick center point"),
|
||||
"pinAlbum": MessageLookupByLibrary.simpleMessage("Pin album"),
|
||||
"pinLock": MessageLookupByLibrary.simpleMessage("PIN lock"),
|
||||
"playOnTv": MessageLookupByLibrary.simpleMessage("Play album on TV"),
|
||||
"playStoreFreeTrialValidTill": m47,
|
||||
"playStoreFreeTrialValidTill": m48,
|
||||
"playstoreSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("PlayStore subscription"),
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain":
|
||||
@@ -1284,14 +1324,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"pleaseContactSupportIfTheProblemPersists":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Please contact support if the problem persists"),
|
||||
"pleaseEmailUsAt": m48,
|
||||
"pleaseEmailUsAt": m49,
|
||||
"pleaseGrantPermissions":
|
||||
MessageLookupByLibrary.simpleMessage("Please grant permissions"),
|
||||
"pleaseLoginAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Please login again"),
|
||||
"pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage(
|
||||
"Please select quick links to remove"),
|
||||
"pleaseSendTheLogsTo": m49,
|
||||
"pleaseSendTheLogsTo": m50,
|
||||
"pleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Please try again"),
|
||||
"pleaseVerifyTheCodeYouHaveEntered":
|
||||
@@ -1317,7 +1357,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Private backups"),
|
||||
"privateSharing":
|
||||
MessageLookupByLibrary.simpleMessage("Private sharing"),
|
||||
"processingImport": m50,
|
||||
"proceed": MessageLookupByLibrary.simpleMessage("Proceed"),
|
||||
"processingImport": m51,
|
||||
"publicLinkCreated":
|
||||
MessageLookupByLibrary.simpleMessage("Public link created"),
|
||||
"publicLinkEnabled":
|
||||
@@ -1327,11 +1368,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"raiseTicket": MessageLookupByLibrary.simpleMessage("Raise ticket"),
|
||||
"rateTheApp": MessageLookupByLibrary.simpleMessage("Rate the app"),
|
||||
"rateUs": MessageLookupByLibrary.simpleMessage("Rate us"),
|
||||
"rateUsOnStore": m51,
|
||||
"rateUsOnStore": m52,
|
||||
"recover": MessageLookupByLibrary.simpleMessage("Recover"),
|
||||
"recoverAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Recover account"),
|
||||
"recoverButton": MessageLookupByLibrary.simpleMessage("Recover"),
|
||||
"recoveryAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Recover account"),
|
||||
"recoveryInitiated":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery initiated"),
|
||||
"recoveryInitiatedDesc": m85,
|
||||
"recoveryKey": MessageLookupByLibrary.simpleMessage("Recovery key"),
|
||||
"recoveryKeyCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
|
||||
"Recovery key copied to clipboard"),
|
||||
@@ -1345,8 +1391,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Recovery key verified"),
|
||||
"recoveryKeyVerifyReason": MessageLookupByLibrary.simpleMessage(
|
||||
"Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly."),
|
||||
"recoveryReady": m86,
|
||||
"recoverySuccessful":
|
||||
MessageLookupByLibrary.simpleMessage("Recovery successful!"),
|
||||
"recoveryWarning": MessageLookupByLibrary.simpleMessage(
|
||||
"A trusted contact is trying to access your account"),
|
||||
"recoveryWarningBody": m87,
|
||||
"recreatePasswordBody": MessageLookupByLibrary.simpleMessage(
|
||||
"The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish)."),
|
||||
"recreatePasswordTitle":
|
||||
@@ -1361,10 +1411,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"1. Give this code to your friends"),
|
||||
"referralStep2": MessageLookupByLibrary.simpleMessage(
|
||||
"2. They sign up for a paid plan"),
|
||||
"referralStep3": m52,
|
||||
"referralStep3": m53,
|
||||
"referrals": MessageLookupByLibrary.simpleMessage("Referrals"),
|
||||
"referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage(
|
||||
"Referrals are currently paused"),
|
||||
"rejectRecovery":
|
||||
MessageLookupByLibrary.simpleMessage("Reject recovery"),
|
||||
"remindToEmptyDeviceTrash": MessageLookupByLibrary.simpleMessage(
|
||||
"Also empty \"Recently Deleted\" from \"Settings\" -> \"Storage\" to claim the freed space"),
|
||||
"remindToEmptyEnteTrash": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1384,10 +1436,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Remove from album?"),
|
||||
"removeFromFavorite":
|
||||
MessageLookupByLibrary.simpleMessage("Remove from favorites"),
|
||||
"removeInvite": MessageLookupByLibrary.simpleMessage("Remove invite"),
|
||||
"removeLink": MessageLookupByLibrary.simpleMessage("Remove link"),
|
||||
"removeParticipant":
|
||||
MessageLookupByLibrary.simpleMessage("Remove participant"),
|
||||
"removeParticipantBody": m53,
|
||||
"removeParticipantBody": m54,
|
||||
"removePersonLabel":
|
||||
MessageLookupByLibrary.simpleMessage("Remove person label"),
|
||||
"removePublicLink":
|
||||
@@ -1398,6 +1451,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Some of the items you are removing were added by other people, and you will lose access to them"),
|
||||
"removeWithQuestionMark":
|
||||
MessageLookupByLibrary.simpleMessage("Remove?"),
|
||||
"removeYourselfAsTrustedContact": MessageLookupByLibrary.simpleMessage(
|
||||
"Remove yourself as trusted contact"),
|
||||
"removingFromFavorites":
|
||||
MessageLookupByLibrary.simpleMessage("Removing from favorites..."),
|
||||
"rename": MessageLookupByLibrary.simpleMessage("Rename"),
|
||||
@@ -1405,7 +1460,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"renameFile": MessageLookupByLibrary.simpleMessage("Rename file"),
|
||||
"renewSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Renew subscription"),
|
||||
"renewsOn": m54,
|
||||
"renewsOn": m55,
|
||||
"reportABug": MessageLookupByLibrary.simpleMessage("Report a bug"),
|
||||
"reportBug": MessageLookupByLibrary.simpleMessage("Report bug"),
|
||||
"resendEmail": MessageLookupByLibrary.simpleMessage("Resend email"),
|
||||
@@ -1480,8 +1535,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Invite people, and you\'ll see all photos shared by them here"),
|
||||
"searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage(
|
||||
"People will be shown here once processing is complete"),
|
||||
"searchResultCount": m55,
|
||||
"searchSectionsLengthMismatch": m56,
|
||||
"searchResultCount": m56,
|
||||
"searchSectionsLengthMismatch": m57,
|
||||
"security": MessageLookupByLibrary.simpleMessage("Security"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
@@ -1516,7 +1571,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Selected items will be deleted from all albums and moved to trash."),
|
||||
"selectedPhotos": m4,
|
||||
"selectedPhotosWithYours": m57,
|
||||
"selectedPhotosWithYours": m58,
|
||||
"send": MessageLookupByLibrary.simpleMessage("Send"),
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("Send email"),
|
||||
"sendInvite": MessageLookupByLibrary.simpleMessage("Send invite"),
|
||||
@@ -1545,16 +1600,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"shareAnAlbumNow":
|
||||
MessageLookupByLibrary.simpleMessage("Share an album now"),
|
||||
"shareLink": MessageLookupByLibrary.simpleMessage("Share link"),
|
||||
"shareMyVerificationID": m58,
|
||||
"shareMyVerificationID": m59,
|
||||
"shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage(
|
||||
"Share only with the people you want"),
|
||||
"shareTextConfirmOthersVerificationID": m5,
|
||||
"shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage(
|
||||
"Download Ente so we can easily share original quality photos and videos\n\nhttps://ente.io"),
|
||||
"shareTextReferralCode": m59,
|
||||
"shareTextReferralCode": m60,
|
||||
"shareWithNonenteUsers":
|
||||
MessageLookupByLibrary.simpleMessage("Share with non-Ente users"),
|
||||
"shareWithPeopleSectionTitle": m60,
|
||||
"shareWithPeopleSectionTitle": m61,
|
||||
"shareYourFirstAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Share your first album"),
|
||||
"sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1565,7 +1620,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("New shared photos"),
|
||||
"sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage(
|
||||
"Receive notifications when someone adds a photo to a shared album that you\'re a part of"),
|
||||
"sharedWith": m61,
|
||||
"sharedWith": m62,
|
||||
"sharedWithMe": MessageLookupByLibrary.simpleMessage("Shared with me"),
|
||||
"sharedWithYou":
|
||||
MessageLookupByLibrary.simpleMessage("Shared with you"),
|
||||
@@ -1580,11 +1635,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Sign out other devices"),
|
||||
"signUpTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>"),
|
||||
"singleFileDeleteFromDevice": m62,
|
||||
"singleFileDeleteFromDevice": m63,
|
||||
"singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage(
|
||||
"It will be deleted from all albums."),
|
||||
"singleFileInBothLocalAndRemote": m63,
|
||||
"singleFileInRemoteOnly": m64,
|
||||
"singleFileInBothLocalAndRemote": m64,
|
||||
"singleFileInRemoteOnly": m65,
|
||||
"skip": MessageLookupByLibrary.simpleMessage("Skip"),
|
||||
"social": MessageLookupByLibrary.simpleMessage("Social"),
|
||||
"someItemsAreInBothEnteAndYourDevice":
|
||||
@@ -1618,6 +1673,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"sortNewestFirst": MessageLookupByLibrary.simpleMessage("Newest first"),
|
||||
"sortOldestFirst": MessageLookupByLibrary.simpleMessage("Oldest first"),
|
||||
"sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Success"),
|
||||
"startAccountRecoveryTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Start recovery"),
|
||||
"startBackup": MessageLookupByLibrary.simpleMessage("Start backup"),
|
||||
"status": MessageLookupByLibrary.simpleMessage("Status"),
|
||||
"stopCastingBody": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1630,10 +1687,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"storageInGB": m1,
|
||||
"storageLimitExceeded":
|
||||
MessageLookupByLibrary.simpleMessage("Storage limit exceeded"),
|
||||
"storageUsageInfo": m65,
|
||||
"storageUsageInfo": m66,
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Strong"),
|
||||
"subAlreadyLinkedErrMessage": m66,
|
||||
"subWillBeCancelledOn": m67,
|
||||
"subAlreadyLinkedErrMessage": m67,
|
||||
"subWillBeCancelledOn": m68,
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("Subscribe"),
|
||||
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
|
||||
"You need an active paid subscription to enable sharing."),
|
||||
@@ -1650,7 +1707,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"suggestFeatures":
|
||||
MessageLookupByLibrary.simpleMessage("Suggest features"),
|
||||
"support": MessageLookupByLibrary.simpleMessage("Support"),
|
||||
"syncProgress": m68,
|
||||
"syncProgress": m69,
|
||||
"syncStopped": MessageLookupByLibrary.simpleMessage("Sync stopped"),
|
||||
"syncing": MessageLookupByLibrary.simpleMessage("Syncing..."),
|
||||
"systemTheme": MessageLookupByLibrary.simpleMessage("System"),
|
||||
@@ -1659,7 +1716,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Tap to enter code"),
|
||||
"tapToUnlock": MessageLookupByLibrary.simpleMessage("Tap to unlock"),
|
||||
"tapToUpload": MessageLookupByLibrary.simpleMessage("Tap to upload"),
|
||||
"tapToUploadIsIgnoredDue": m69,
|
||||
"tapToUploadIsIgnoredDue": m70,
|
||||
"tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage(
|
||||
"It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team."),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Terminate"),
|
||||
@@ -1682,7 +1739,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"theseItemsWillBeDeletedFromYourDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"These items will be deleted from your device."),
|
||||
"theyAlsoGetXGb": m70,
|
||||
"theyAlsoGetXGb": m71,
|
||||
"theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage(
|
||||
"They will be deleted from all albums."),
|
||||
"thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1698,7 +1755,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"This email is already in use"),
|
||||
"thisImageHasNoExifData":
|
||||
MessageLookupByLibrary.simpleMessage("This image has no exif data"),
|
||||
"thisIsPersonVerificationId": m71,
|
||||
"thisIsPersonVerificationId": m72,
|
||||
"thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage(
|
||||
"This is your Verification ID"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
@@ -1722,8 +1779,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"total": MessageLookupByLibrary.simpleMessage("total"),
|
||||
"totalSize": MessageLookupByLibrary.simpleMessage("Total size"),
|
||||
"trash": MessageLookupByLibrary.simpleMessage("Trash"),
|
||||
"trashDaysLeft": m72,
|
||||
"trashDaysLeft": m73,
|
||||
"trim": MessageLookupByLibrary.simpleMessage("Trim"),
|
||||
"trustedContacts":
|
||||
MessageLookupByLibrary.simpleMessage("Trusted contacts"),
|
||||
"trustedInviteBody": m88,
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Try again"),
|
||||
"turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage(
|
||||
"Turn on backup to automatically upload files added to this device folder to Ente."),
|
||||
@@ -1741,7 +1801,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Two-factor authentication successfully reset"),
|
||||
"twofactorSetup":
|
||||
MessageLookupByLibrary.simpleMessage("Two-factor setup"),
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m73,
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m74,
|
||||
"unarchive": MessageLookupByLibrary.simpleMessage("Unarchive"),
|
||||
"unarchiveAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Unarchive album"),
|
||||
@@ -1764,10 +1824,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"updatingFolderSelection": MessageLookupByLibrary.simpleMessage(
|
||||
"Updating folder selection..."),
|
||||
"upgrade": MessageLookupByLibrary.simpleMessage("Upgrade"),
|
||||
"uploadIsIgnoredDueToIgnorereason": m74,
|
||||
"uploadIsIgnoredDueToIgnorereason": m75,
|
||||
"uploadingFilesToAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Uploading files to album..."),
|
||||
"uploadingMultipleMemories": m75,
|
||||
"uploadingMultipleMemories": m76,
|
||||
"uploadingSingleMemory":
|
||||
MessageLookupByLibrary.simpleMessage("Preserving 1 memory..."),
|
||||
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1783,7 +1843,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"useSelectedPhoto":
|
||||
MessageLookupByLibrary.simpleMessage("Use selected photo"),
|
||||
"usedSpace": MessageLookupByLibrary.simpleMessage("Used space"),
|
||||
"validTill": m76,
|
||||
"validTill": m77,
|
||||
"verificationFailedPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Verification failed, please try again"),
|
||||
@@ -1791,7 +1851,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Verification ID"),
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Verify"),
|
||||
"verifyEmail": MessageLookupByLibrary.simpleMessage("Verify email"),
|
||||
"verifyEmailID": m77,
|
||||
"verifyEmailID": m78,
|
||||
"verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verify"),
|
||||
"verifyPasskey": MessageLookupByLibrary.simpleMessage("Verify passkey"),
|
||||
"verifyPassword":
|
||||
@@ -1815,13 +1875,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"viewRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("View recovery key"),
|
||||
"viewer": MessageLookupByLibrary.simpleMessage("Viewer"),
|
||||
"viewersSuccessfullyAdded": m78,
|
||||
"viewersSuccessfullyAdded": m79,
|
||||
"visitWebToManage": MessageLookupByLibrary.simpleMessage(
|
||||
"Please visit web.ente.io to manage your subscription"),
|
||||
"waitingForVerification":
|
||||
MessageLookupByLibrary.simpleMessage("Waiting for verification..."),
|
||||
"waitingForWifi":
|
||||
MessageLookupByLibrary.simpleMessage("Waiting for WiFi..."),
|
||||
"warning": MessageLookupByLibrary.simpleMessage("Warning"),
|
||||
"weAreOpenSource":
|
||||
MessageLookupByLibrary.simpleMessage("We are open source!"),
|
||||
"weDontSupportEditingPhotosAndAlbumsThatYouDont":
|
||||
@@ -1831,9 +1892,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"weakStrength": MessageLookupByLibrary.simpleMessage("Weak"),
|
||||
"welcomeBack": MessageLookupByLibrary.simpleMessage("Welcome back!"),
|
||||
"whatsNew": MessageLookupByLibrary.simpleMessage("What\'s new"),
|
||||
"whyAddTrustContact": MessageLookupByLibrary.simpleMessage(
|
||||
"Trusted contact can help in recovering your data."),
|
||||
"yearShort": MessageLookupByLibrary.simpleMessage("yr"),
|
||||
"yearly": MessageLookupByLibrary.simpleMessage("Yearly"),
|
||||
"yearsAgo": m79,
|
||||
"yearsAgo": m80,
|
||||
"yes": MessageLookupByLibrary.simpleMessage("Yes"),
|
||||
"yesCancel": MessageLookupByLibrary.simpleMessage("Yes, cancel"),
|
||||
"yesConvertToViewer":
|
||||
@@ -1865,7 +1928,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"You cannot share with yourself"),
|
||||
"youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage(
|
||||
"You don\'t have any archived items."),
|
||||
"youHaveSuccessfullyFreedUp": m80,
|
||||
"youHaveSuccessfullyFreedUp": m81,
|
||||
"yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage(
|
||||
"Your account has been deleted"),
|
||||
"yourMap": MessageLookupByLibrary.simpleMessage("Your map"),
|
||||
|
||||
366
mobile/lib/generated/intl/messages_es.dart
generated
16
mobile/lib/generated/intl/messages_et.dart
generated
@@ -35,14 +35,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"albumOwner": MessageLookupByLibrary.simpleMessage("Omanik"),
|
||||
"albumUpdated":
|
||||
MessageLookupByLibrary.simpleMessage("Albumit on uuendatud"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"allowDownloads":
|
||||
MessageLookupByLibrary.simpleMessage("Luba allalaadimised"),
|
||||
"appleId": MessageLookupByLibrary.simpleMessage("Apple ID"),
|
||||
"apply": MessageLookupByLibrary.simpleMessage("Rakenda"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"blog": MessageLookupByLibrary.simpleMessage("Blogi"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("Loobu"),
|
||||
"changeEmail": MessageLookupByLibrary.simpleMessage("Muuda e-posti"),
|
||||
@@ -114,8 +110,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Sisesta oma parool"),
|
||||
"exportYourData":
|
||||
MessageLookupByLibrary.simpleMessage("Ekspordi oma andmed"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"faq": MessageLookupByLibrary.simpleMessage("KKK"),
|
||||
"faqs": MessageLookupByLibrary.simpleMessage("KKK"),
|
||||
"feedback": MessageLookupByLibrary.simpleMessage("Tagasiside"),
|
||||
@@ -175,11 +169,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"oops": MessageLookupByLibrary.simpleMessage("Oih"),
|
||||
"oopsSomethingWentWrong":
|
||||
MessageLookupByLibrary.simpleMessage("Oih, midagi läks valesti"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("Parool"),
|
||||
"photoSmallCase": MessageLookupByLibrary.simpleMessage("foto"),
|
||||
"pleaseTryAgain":
|
||||
@@ -215,8 +204,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Skaneeri seda QR koodi\noma autentimisrakendusega"),
|
||||
"security": MessageLookupByLibrary.simpleMessage("Turvalisus"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("Vali kõik"),
|
||||
"selectLanguage": MessageLookupByLibrary.simpleMessage("Vali keel"),
|
||||
"selectReason": MessageLookupByLibrary.simpleMessage("Vali põhjus"),
|
||||
@@ -253,9 +240,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"terms": MessageLookupByLibrary.simpleMessage("Tingimused"),
|
||||
"termsOfServicesTitle":
|
||||
MessageLookupByLibrary.simpleMessage("Tingimused"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("Teema"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("See seade"),
|
||||
"trash": MessageLookupByLibrary.simpleMessage("Prügikast"),
|
||||
|
||||
28
mobile/lib/generated/intl/messages_fa.dart
generated
@@ -31,13 +31,13 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m0(passwordStrengthValue) =>
|
||||
"قدرت رمز عبور: ${passwordStrengthValue}";
|
||||
|
||||
static String m51(storeName) => "به ما در ${storeName} امتیاز دهید";
|
||||
static String m52(storeName) => "به ما در ${storeName} امتیاز دهید";
|
||||
|
||||
static String m65(
|
||||
static String m66(
|
||||
usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) =>
|
||||
"${usedAmount} ${usedStorageUnit} از ${totalAmount} ${totalStorageUnit} استفاده شده";
|
||||
|
||||
static String m77(email) => "تایید ${email}";
|
||||
static String m78(email) => "تایید ${email}";
|
||||
|
||||
static String m2(email) =>
|
||||
"ما یک ایمیل به <green>${email}</green> ارسال کردهایم";
|
||||
@@ -62,13 +62,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"addedAs": MessageLookupByLibrary.simpleMessage("اضافه شده به عنوان"),
|
||||
"advanced": MessageLookupByLibrary.simpleMessage("پیشرفته"),
|
||||
"albumUpdated": MessageLookupByLibrary.simpleMessage("آلبوم بهروز شد"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"به افراد که این پیوند را دارند، اجازه دهید عکسها را به آلبوم اشتراک گذاری شده اضافه کنند."),
|
||||
"allowAddingPhotos":
|
||||
MessageLookupByLibrary.simpleMessage("اجازه اضافه کردن عکس"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage(
|
||||
"به افراد اجازه دهید عکس اضافه کنند"),
|
||||
"androidBiometricHint":
|
||||
@@ -93,7 +90,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backedUpFolders":
|
||||
MessageLookupByLibrary.simpleMessage("پوشههای پشتیبان گیری شده"),
|
||||
"backup": MessageLookupByLibrary.simpleMessage("پشتیبان گیری"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"blog": MessageLookupByLibrary.simpleMessage("وبلاگ"),
|
||||
"cancel": MessageLookupByLibrary.simpleMessage("لغو"),
|
||||
"cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -203,8 +199,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"error": MessageLookupByLibrary.simpleMessage("خطا"),
|
||||
"everywhere": MessageLookupByLibrary.simpleMessage("همه جا"),
|
||||
"existingUser": MessageLookupByLibrary.simpleMessage("کاربر موجود"),
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"familyPlanPortalTitle":
|
||||
MessageLookupByLibrary.simpleMessage("خانوادگی"),
|
||||
"familyPlans":
|
||||
@@ -275,11 +269,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"notifications": MessageLookupByLibrary.simpleMessage("آگاهسازیها"),
|
||||
"ok": MessageLookupByLibrary.simpleMessage("تایید"),
|
||||
"oops": MessageLookupByLibrary.simpleMessage("اوه"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"password": MessageLookupByLibrary.simpleMessage("رمز عبور"),
|
||||
"passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage(
|
||||
"رمز عبور با موفقیت تغییر کرد"),
|
||||
@@ -303,7 +292,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("پشتیبان گیری خصوصی"),
|
||||
"privateSharing":
|
||||
MessageLookupByLibrary.simpleMessage("اشتراک گذاری خصوصی"),
|
||||
"rateUsOnStore": m51,
|
||||
"rateUsOnStore": m52,
|
||||
"recover": MessageLookupByLibrary.simpleMessage("بازیابی"),
|
||||
"recoverAccount":
|
||||
MessageLookupByLibrary.simpleMessage("بازیابی حساب کاربری"),
|
||||
@@ -336,8 +325,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"saveKey": MessageLookupByLibrary.simpleMessage("ذخیره کلید"),
|
||||
"search": MessageLookupByLibrary.simpleMessage("جستجو"),
|
||||
"security": MessageLookupByLibrary.simpleMessage("امنیت"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"selectAll": MessageLookupByLibrary.simpleMessage("انتخاب همه"),
|
||||
"selectFoldersForBackup": MessageLookupByLibrary.simpleMessage(
|
||||
"پوشهها را برای پشتیبان گیری انتخاب کنید"),
|
||||
@@ -381,7 +368,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"storageBreakupFamily":
|
||||
MessageLookupByLibrary.simpleMessage("خانوادگی"),
|
||||
"storageBreakupYou": MessageLookupByLibrary.simpleMessage("شما"),
|
||||
"storageUsageInfo": m65,
|
||||
"storageUsageInfo": m66,
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("قوی"),
|
||||
"support": MessageLookupByLibrary.simpleMessage("پشتیبانی"),
|
||||
"systemTheme": MessageLookupByLibrary.simpleMessage("سیستم"),
|
||||
@@ -397,9 +384,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("شرایط و مقررات"),
|
||||
"theDownloadCouldNotBeCompleted":
|
||||
MessageLookupByLibrary.simpleMessage("دانلود کامل نشد"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"theme": MessageLookupByLibrary.simpleMessage("تم"),
|
||||
"thisDevice": MessageLookupByLibrary.simpleMessage("این دستگاه"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
@@ -425,7 +409,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"از کلید بازیابی استفاده کنید"),
|
||||
"verify": MessageLookupByLibrary.simpleMessage("تایید"),
|
||||
"verifyEmail": MessageLookupByLibrary.simpleMessage("تایید ایمیل"),
|
||||
"verifyEmailID": m77,
|
||||
"verifyEmailID": m78,
|
||||
"verifyIDLabel": MessageLookupByLibrary.simpleMessage("تایید"),
|
||||
"verifyPassword":
|
||||
MessageLookupByLibrary.simpleMessage("تایید رمز عبور"),
|
||||
|
||||
187
mobile/lib/generated/intl/messages_fr.dart
generated
@@ -42,7 +42,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m13(versionValue) => "Version : ${versionValue}";
|
||||
|
||||
static String m14(freeAmount, storageUnit) =>
|
||||
"${freeAmount} ${storageUnit} gratuit";
|
||||
"${freeAmount} ${storageUnit} libre";
|
||||
|
||||
static String m15(paymentProvider) =>
|
||||
"Veuillez d\'abord annuler votre abonnement existant de ${paymentProvider}";
|
||||
@@ -62,7 +62,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
static String m18(albumName) => "Lien collaboratif créé pour ${albumName}";
|
||||
|
||||
static String m81(count) =>
|
||||
static String m82(count) =>
|
||||
"${Intl.plural(count, zero: '0 collaborateur ajouté', one: '1 collaborateur ajouté', other: '${count} collaborateurs ajoutés')}";
|
||||
|
||||
static String m19(familyAdminEmail) =>
|
||||
@@ -126,7 +126,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m40(expiryTime) => "Le lien expirera le ${expiryTime}";
|
||||
|
||||
static String m3(count, formattedCount) =>
|
||||
"${Intl.plural(count, one: '${formattedCount} mémoire', other: '${formattedCount} souvenirs')}";
|
||||
"${Intl.plural(count, one: '${formattedCount} souvenir', other: '${formattedCount} souvenirs')}";
|
||||
|
||||
static String m41(count) =>
|
||||
"${Intl.plural(count, one: 'Déplacez l\'objet', other: 'Déplacez des objets')}";
|
||||
@@ -146,108 +146,108 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
static String m46(providerName) =>
|
||||
"Veuillez contacter le support ${providerName} si vous avez été facturé";
|
||||
|
||||
static String m82(count) =>
|
||||
static String m47(count) =>
|
||||
"${Intl.plural(count, zero: '0 photo', one: '1 photo', other: '${count} photos')}";
|
||||
|
||||
static String m47(endDate) =>
|
||||
static String m48(endDate) =>
|
||||
"Essai gratuit valable jusqu\'à ${endDate}.\nVous pouvez choisir un plan payant par la suite.";
|
||||
|
||||
static String m48(toEmail) => "Merci de nous envoyer un e-mail à ${toEmail}";
|
||||
static String m49(toEmail) => "Merci de nous envoyer un e-mail à ${toEmail}";
|
||||
|
||||
static String m49(toEmail) => "Envoyez les logs à ${toEmail}";
|
||||
static String m50(toEmail) => "Envoyez les logs à ${toEmail}";
|
||||
|
||||
static String m50(folderName) => "Traitement de ${folderName}...";
|
||||
static String m51(folderName) => "Traitement de ${folderName}...";
|
||||
|
||||
static String m51(storeName) => "Notez-nous sur ${storeName}";
|
||||
static String m52(storeName) => "Notez-nous sur ${storeName}";
|
||||
|
||||
static String m52(storageInGB) =>
|
||||
static String m53(storageInGB) =>
|
||||
"3. Vous recevez tous les deux ${storageInGB} GB* gratuits";
|
||||
|
||||
static String m53(userEmail) =>
|
||||
static String m54(userEmail) =>
|
||||
"${userEmail} sera retiré de cet album partagé\n\nToutes les photos ajoutées par eux seront également retirées de l\'album";
|
||||
|
||||
static String m54(endDate) => "Renouvellement le ${endDate}";
|
||||
static String m55(endDate) => "Renouvellement le ${endDate}";
|
||||
|
||||
static String m55(count) =>
|
||||
static String m56(count) =>
|
||||
"${Intl.plural(count, one: '${count} résultat trouvé', other: '${count} résultats trouvés')}";
|
||||
|
||||
static String m56(snapshotLenght, searchLenght) =>
|
||||
static String m57(snapshotLenght, searchLenght) =>
|
||||
"Incompatibilité de la longueur des sections: ${snapshotLenght} != ${searchLenght}";
|
||||
|
||||
static String m4(count) => "${count} sélectionné(s)";
|
||||
|
||||
static String m57(count, yourCount) =>
|
||||
static String m58(count, yourCount) =>
|
||||
"${count} sélectionné(s) (${yourCount} à vous)";
|
||||
|
||||
static String m58(verificationID) =>
|
||||
static String m59(verificationID) =>
|
||||
"Voici mon ID de vérification : ${verificationID} pour ente.io.";
|
||||
|
||||
static String m5(verificationID) =>
|
||||
"Hé, pouvez-vous confirmer qu\'il s\'agit de votre ID de vérification ente.io : ${verificationID}";
|
||||
|
||||
static String m59(referralCode, referralStorageInGB) =>
|
||||
static String m60(referralCode, referralStorageInGB) =>
|
||||
"Code de parrainage Ente : ${referralCode} \n\nValidez le dans Paramètres → Général → Références pour obtenir ${referralStorageInGB} Go gratuitement après votre inscription à un plan payant\n\nhttps://ente.io";
|
||||
|
||||
static String m60(numberOfPeople) =>
|
||||
static String m61(numberOfPeople) =>
|
||||
"${Intl.plural(numberOfPeople, zero: 'Partagez avec des personnes spécifiques', one: 'Partagé avec 1 personne', other: 'Partagé avec ${numberOfPeople} personnes')}";
|
||||
|
||||
static String m61(emailIDs) => "Partagé avec ${emailIDs}";
|
||||
|
||||
static String m62(fileType) =>
|
||||
"Elle ${fileType} sera supprimée de votre appareil.";
|
||||
static String m62(emailIDs) => "Partagé avec ${emailIDs}";
|
||||
|
||||
static String m63(fileType) =>
|
||||
"Elle ${fileType} sera supprimée de votre appareil.";
|
||||
|
||||
static String m64(fileType) =>
|
||||
"Cette ${fileType} est à la fois sur ente et sur votre appareil.";
|
||||
|
||||
static String m64(fileType) => "Cette ${fileType} sera supprimée de l\'Ente.";
|
||||
static String m65(fileType) => "Cette ${fileType} sera supprimée de l\'Ente.";
|
||||
|
||||
static String m1(storageAmountInGB) => "${storageAmountInGB} Go";
|
||||
|
||||
static String m65(
|
||||
static String m66(
|
||||
usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) =>
|
||||
"${usedAmount} ${usedStorageUnit} sur ${totalAmount} ${totalStorageUnit} utilisé";
|
||||
|
||||
static String m66(id) =>
|
||||
static String m67(id) =>
|
||||
"Votre ${id} est déjà lié à un autre compte Ente.\nSi vous souhaitez utiliser votre ${id} avec ce compte, veuillez contacter notre support";
|
||||
|
||||
static String m67(endDate) => "Votre abonnement sera annulé le ${endDate}";
|
||||
static String m68(endDate) => "Votre abonnement sera annulé le ${endDate}";
|
||||
|
||||
static String m68(completed, total) =>
|
||||
static String m69(completed, total) =>
|
||||
"${completed}/${total} souvenirs conservés";
|
||||
|
||||
static String m69(ignoreReason) =>
|
||||
static String m70(ignoreReason) =>
|
||||
"Appuyer pour envoyer, l\'envoi est actuellement ignoré en raison de ${ignoreReason}";
|
||||
|
||||
static String m70(storageAmountInGB) =>
|
||||
static String m71(storageAmountInGB) =>
|
||||
"Ils obtiennent aussi ${storageAmountInGB} Go";
|
||||
|
||||
static String m71(email) => "Ceci est l\'ID de vérification de ${email}";
|
||||
static String m72(email) => "Ceci est l\'ID de vérification de ${email}";
|
||||
|
||||
static String m72(count) =>
|
||||
"${Intl.plural(count, zero: '0 jour', one: '1 jour', other: '${count} jours')}";
|
||||
static String m73(count) =>
|
||||
"${Intl.plural(count, zero: 'Bientôt', one: '1 jour', other: '${count} jours')}";
|
||||
|
||||
static String m73(galleryType) =>
|
||||
static String m74(galleryType) =>
|
||||
"Les galeries de type \'${galleryType}\' ne peuvent être renommées";
|
||||
|
||||
static String m74(ignoreReason) =>
|
||||
static String m75(ignoreReason) =>
|
||||
"L\'envoi est ignoré en raison de ${ignoreReason}";
|
||||
|
||||
static String m75(count) => "Sauvegarde ${count} souvenirs...";
|
||||
static String m76(count) => "Sauvegarde ${count} souvenirs...";
|
||||
|
||||
static String m76(endDate) => "Valable jusqu\'au ${endDate}";
|
||||
static String m77(endDate) => "Valable jusqu\'au ${endDate}";
|
||||
|
||||
static String m77(email) => "Vérifier ${email}";
|
||||
static String m78(email) => "Vérifier ${email}";
|
||||
|
||||
static String m78(count) =>
|
||||
static String m79(count) =>
|
||||
"${Intl.plural(count, zero: '0 observateur ajouté', one: '1 observateur ajouté', other: '${count} observateurs ajoutés')}";
|
||||
|
||||
static String m2(email) =>
|
||||
"Nous avons envoyé un e-mail à <green>${email}</green>";
|
||||
|
||||
static String m79(count) =>
|
||||
static String m80(count) =>
|
||||
"${Intl.plural(count, one: 'il y a ${count} an', other: 'il y a ${count} ans')}";
|
||||
|
||||
static String m80(storageSaved) =>
|
||||
static String m81(storageSaved) =>
|
||||
"Vous avez libéré ${storageSaved} avec succès !";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
@@ -324,16 +324,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"albums": MessageLookupByLibrary.simpleMessage("Albums"),
|
||||
"allClear": MessageLookupByLibrary.simpleMessage("✨ Tout est effacé"),
|
||||
"allMemoriesPreserved": MessageLookupByLibrary.simpleMessage(
|
||||
"Tous les souvenirs conservés"),
|
||||
"Tous les souvenirs sont conservés"),
|
||||
"allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage(
|
||||
"Tous les groupements pour cette personne seront réinitialisés, et vous perdrez toutes les suggestions faites pour cette personne"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Allow"),
|
||||
"allow": MessageLookupByLibrary.simpleMessage("Autoriser"),
|
||||
"allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage(
|
||||
"Autoriser les personnes avec le lien à ajouter des photos à l\'album partagé."),
|
||||
"allowAddingPhotos": MessageLookupByLibrary.simpleMessage(
|
||||
"Autoriser l\'ajout de photos"),
|
||||
"allowAppToOpenSharedAlbumLinks": MessageLookupByLibrary.simpleMessage(
|
||||
"Allow app to open shared album links"),
|
||||
"Autoriser l\'application à ouvrir les liens d\'albums partagés"),
|
||||
"allowDownloads": MessageLookupByLibrary.simpleMessage(
|
||||
"Autoriser les téléchargements"),
|
||||
"allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -450,7 +450,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"backup": MessageLookupByLibrary.simpleMessage("Sauvegarde"),
|
||||
"backupFailed":
|
||||
MessageLookupByLibrary.simpleMessage("Échec de la sauvegarde"),
|
||||
"backupFile": MessageLookupByLibrary.simpleMessage("Backup file"),
|
||||
"backupFile":
|
||||
MessageLookupByLibrary.simpleMessage("Sauvegarder le fichier"),
|
||||
"backupOverMobileData": MessageLookupByLibrary.simpleMessage(
|
||||
"Sauvegarde sur données mobiles"),
|
||||
"backupSettings":
|
||||
@@ -567,7 +568,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Les collaborateurs peuvent ajouter des photos et des vidéos à l\'album partagé."),
|
||||
"collaboratorsSuccessfullyAdded": m81,
|
||||
"collaboratorsSuccessfullyAdded": m82,
|
||||
"collageLayout": MessageLookupByLibrary.simpleMessage("Disposition"),
|
||||
"collageSaved": MessageLookupByLibrary.simpleMessage(
|
||||
"Collage sauvegardé dans la galerie"),
|
||||
@@ -686,7 +687,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"deleteFromDevice":
|
||||
MessageLookupByLibrary.simpleMessage("Supprimer de l\'appareil"),
|
||||
"deleteFromEnte":
|
||||
MessageLookupByLibrary.simpleMessage("Supprimé de Ente"),
|
||||
MessageLookupByLibrary.simpleMessage("Supprimer de Ente"),
|
||||
"deleteItemCount": m22,
|
||||
"deleteLocation":
|
||||
MessageLookupByLibrary.simpleMessage("Supprimer la localisation"),
|
||||
@@ -877,7 +878,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Photos supplémentaires trouvées"),
|
||||
"extraPhotosFoundFor": m30,
|
||||
"faceNotClusteredYet": MessageLookupByLibrary.simpleMessage(
|
||||
"Face not clustered yet, please come back later"),
|
||||
"Ce visage n\'a pas encore été regroupé, veuillez revenir plus tard"),
|
||||
"faceRecognition":
|
||||
MessageLookupByLibrary.simpleMessage("Reconnaissance faciale"),
|
||||
"faces": MessageLookupByLibrary.simpleMessage("Visages"),
|
||||
@@ -1135,6 +1136,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Votre session a expiré. Veuillez vous reconnecter."),
|
||||
"loginTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"En cliquant sur connecter, j\'accepte les <u-terms>conditions d\'utilisation</u-terms> et la <u-policy>politique de confidentialité</u-policy>"),
|
||||
"loginWithTOTP":
|
||||
MessageLookupByLibrary.simpleMessage("Se connecter avec TOTP"),
|
||||
"logout": MessageLookupByLibrary.simpleMessage("Déconnexion"),
|
||||
"logsDialogBody": MessageLookupByLibrary.simpleMessage(
|
||||
"Cela enverra des logs pour nous aider à déboguer votre problème. Veuillez noter que les noms de fichiers seront inclus pour aider à suivre les problèmes avec des fichiers spécifiques."),
|
||||
@@ -1157,7 +1160,9 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"La recherche magique permet de rechercher des photos par leur contenu, par exemple \'fleur\', \'voiture rouge\', \'documents d\'identité\'"),
|
||||
"manage": MessageLookupByLibrary.simpleMessage("Gérer"),
|
||||
"manageDeviceStorage": MessageLookupByLibrary.simpleMessage(
|
||||
"Gérer le stockage de l\'appareil"),
|
||||
"Gérer le cache de l\'appareil"),
|
||||
"manageDeviceStorageDesc":
|
||||
MessageLookupByLibrary.simpleMessage("Examiner et vider le cache."),
|
||||
"manageFamily":
|
||||
MessageLookupByLibrary.simpleMessage("Gérer la famille"),
|
||||
"manageLink": MessageLookupByLibrary.simpleMessage("Gérer le lien"),
|
||||
@@ -1277,11 +1282,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Oups, impossible d\'enregistrer les modifications"),
|
||||
"oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage(
|
||||
"Oups, une erreur est arrivée"),
|
||||
"openAlbumInBrowser":
|
||||
MessageLookupByLibrary.simpleMessage("Open album in browser"),
|
||||
"openAlbumInBrowser": MessageLookupByLibrary.simpleMessage(
|
||||
"Ouvrir l\'album dans le navigateur"),
|
||||
"openAlbumInBrowserTitle": MessageLookupByLibrary.simpleMessage(
|
||||
"Please use the web app to add photos to this album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Open file"),
|
||||
"Veuillez utiliser l\'application web pour ajouter des photos à cet album"),
|
||||
"openFile": MessageLookupByLibrary.simpleMessage("Ouvrir le fichier"),
|
||||
"openSettings":
|
||||
MessageLookupByLibrary.simpleMessage("Ouvrir les paramètres"),
|
||||
"openTheItem":
|
||||
@@ -1346,7 +1351,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"photosAddedByYouWillBeRemovedFromTheAlbum":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Les photos ajoutées par vous seront retirées de l\'album"),
|
||||
"photosCount": m82,
|
||||
"photosCount": m47,
|
||||
"pickCenterPoint": MessageLookupByLibrary.simpleMessage(
|
||||
"Sélectionner le point central"),
|
||||
"pinAlbum": MessageLookupByLibrary.simpleMessage("Épingler l\'album"),
|
||||
@@ -1354,7 +1359,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Verrouillage du code PIN"),
|
||||
"playOnTv":
|
||||
MessageLookupByLibrary.simpleMessage("Lire l\'album sur la TV"),
|
||||
"playStoreFreeTrialValidTill": m47,
|
||||
"playStoreFreeTrialValidTill": m48,
|
||||
"playstoreSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Abonnement au PlayStore"),
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain":
|
||||
@@ -1366,14 +1371,14 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"pleaseContactSupportIfTheProblemPersists":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Merci de contacter l\'assistance si cette erreur persiste"),
|
||||
"pleaseEmailUsAt": m48,
|
||||
"pleaseEmailUsAt": m49,
|
||||
"pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage(
|
||||
"Veuillez accorder la permission"),
|
||||
"pleaseLoginAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Veuillez vous reconnecter"),
|
||||
"pleaseSelectQuickLinksToRemove": MessageLookupByLibrary.simpleMessage(
|
||||
"Veuillez sélectionner les liens rapides à supprimer"),
|
||||
"pleaseSendTheLogsTo": m49,
|
||||
"pleaseSendTheLogsTo": m50,
|
||||
"pleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage("Veuillez réessayer"),
|
||||
"pleaseVerifyTheCodeYouHaveEntered":
|
||||
@@ -1399,7 +1404,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"privateBackups":
|
||||
MessageLookupByLibrary.simpleMessage("Sauvegardes privées"),
|
||||
"privateSharing": MessageLookupByLibrary.simpleMessage("Partage privé"),
|
||||
"processingImport": m50,
|
||||
"processingImport": m51,
|
||||
"publicLinkCreated":
|
||||
MessageLookupByLibrary.simpleMessage("Lien public créé"),
|
||||
"publicLinkEnabled":
|
||||
@@ -1410,7 +1415,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"rateTheApp":
|
||||
MessageLookupByLibrary.simpleMessage("Évaluer l\'application"),
|
||||
"rateUs": MessageLookupByLibrary.simpleMessage("Évaluez-nous"),
|
||||
"rateUsOnStore": m51,
|
||||
"rateUsOnStore": m52,
|
||||
"recover": MessageLookupByLibrary.simpleMessage("Récupérer"),
|
||||
"recoverAccount":
|
||||
MessageLookupByLibrary.simpleMessage("Récupérer un compte"),
|
||||
@@ -1440,12 +1445,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"reenterPin":
|
||||
MessageLookupByLibrary.simpleMessage("Ressaisir le code PIN"),
|
||||
"referFriendsAnd2xYourPlan": MessageLookupByLibrary.simpleMessage(
|
||||
"Parrainez des amis et 2x votre abonnement"),
|
||||
"Parrainez des amis et doublez votre abonnement"),
|
||||
"referralStep1": MessageLookupByLibrary.simpleMessage(
|
||||
"1. Donnez ce code à vos amis"),
|
||||
"referralStep2": MessageLookupByLibrary.simpleMessage(
|
||||
"2. Ils s\'inscrivent à une offre payante"),
|
||||
"referralStep3": m52,
|
||||
"referralStep3": m53,
|
||||
"referrals": MessageLookupByLibrary.simpleMessage("Parrainages"),
|
||||
"referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage(
|
||||
"Les recommandations sont actuellement en pause"),
|
||||
@@ -1473,7 +1478,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"removeLink": MessageLookupByLibrary.simpleMessage("Supprimer le lien"),
|
||||
"removeParticipant":
|
||||
MessageLookupByLibrary.simpleMessage("Supprimer le participant"),
|
||||
"removeParticipantBody": m53,
|
||||
"removeParticipantBody": m54,
|
||||
"removePersonLabel": MessageLookupByLibrary.simpleMessage(
|
||||
"Supprimer le libellé d\'une personne"),
|
||||
"removePublicLink":
|
||||
@@ -1493,7 +1498,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Renommer le fichier"),
|
||||
"renewSubscription":
|
||||
MessageLookupByLibrary.simpleMessage("Renouveler l’abonnement"),
|
||||
"renewsOn": m54,
|
||||
"renewsOn": m55,
|
||||
"reportABug": MessageLookupByLibrary.simpleMessage("Signaler un bug"),
|
||||
"reportBug": MessageLookupByLibrary.simpleMessage("Signaler un bug"),
|
||||
"resendEmail":
|
||||
@@ -1575,11 +1580,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Invitez des personnes, et vous verrez ici toutes les photos qu\'elles partagent"),
|
||||
"searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage(
|
||||
"Les personnes seront affichées ici une fois le traitement terminé"),
|
||||
"searchResultCount": m55,
|
||||
"searchSectionsLengthMismatch": m56,
|
||||
"searchResultCount": m56,
|
||||
"searchSectionsLengthMismatch": m57,
|
||||
"security": MessageLookupByLibrary.simpleMessage("Sécurité"),
|
||||
"seePublicAlbumLinksInApp": MessageLookupByLibrary.simpleMessage(
|
||||
"See public album links in app"),
|
||||
"Ouvrir les liens des albums publics dans l\'application"),
|
||||
"selectALocation":
|
||||
MessageLookupByLibrary.simpleMessage("Sélectionnez un emplacement"),
|
||||
"selectALocationFirst": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1613,7 +1618,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Les éléments sélectionnés seront supprimés de tous les albums et déplacés dans la corbeille."),
|
||||
"selectedPhotos": m4,
|
||||
"selectedPhotosWithYours": m57,
|
||||
"selectedPhotosWithYours": m58,
|
||||
"send": MessageLookupByLibrary.simpleMessage("Envoyer"),
|
||||
"sendEmail": MessageLookupByLibrary.simpleMessage("Envoyer un e-mail"),
|
||||
"sendInvite":
|
||||
@@ -1647,16 +1652,16 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"shareAnAlbumNow": MessageLookupByLibrary.simpleMessage(
|
||||
"Partagez un album maintenant"),
|
||||
"shareLink": MessageLookupByLibrary.simpleMessage("Partager le lien"),
|
||||
"shareMyVerificationID": m58,
|
||||
"shareMyVerificationID": m59,
|
||||
"shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage(
|
||||
"Partager uniquement avec les personnes que vous voulez"),
|
||||
"shareTextConfirmOthersVerificationID": m5,
|
||||
"shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage(
|
||||
"Téléchargez Ente pour que nous puissions facilement partager des photos et des vidéos de qualité originale\n\nhttps://ente.io"),
|
||||
"shareTextReferralCode": m59,
|
||||
"shareTextReferralCode": m60,
|
||||
"shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage(
|
||||
"Partager avec des utilisateurs non-Ente"),
|
||||
"shareWithPeopleSectionTitle": m60,
|
||||
"shareWithPeopleSectionTitle": m61,
|
||||
"shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage(
|
||||
"Partagez votre premier album"),
|
||||
"sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1667,7 +1672,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Nouvelles photos partagées"),
|
||||
"sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage(
|
||||
"Recevoir des notifications quand quelqu\'un ajoute une photo à un album partagé dont vous faites partie"),
|
||||
"sharedWith": m61,
|
||||
"sharedWith": m62,
|
||||
"sharedWithMe":
|
||||
MessageLookupByLibrary.simpleMessage("Partagés avec moi"),
|
||||
"sharedWithYou":
|
||||
@@ -1685,11 +1690,11 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Déconnecter les autres appareils"),
|
||||
"signUpTerms": MessageLookupByLibrary.simpleMessage(
|
||||
"J\'accepte les <u-terms>conditions d\'utilisation</u-terms> et la <u-policy>politique de confidentialité</u-policy>"),
|
||||
"singleFileDeleteFromDevice": m62,
|
||||
"singleFileDeleteFromDevice": m63,
|
||||
"singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage(
|
||||
"Elle sera supprimée de tous les albums."),
|
||||
"singleFileInBothLocalAndRemote": m63,
|
||||
"singleFileInRemoteOnly": m64,
|
||||
"singleFileInBothLocalAndRemote": m64,
|
||||
"singleFileInRemoteOnly": m65,
|
||||
"skip": MessageLookupByLibrary.simpleMessage("Ignorer"),
|
||||
"social": MessageLookupByLibrary.simpleMessage("Réseaux Sociaux"),
|
||||
"someItemsAreInBothEnteAndYourDevice":
|
||||
@@ -1738,10 +1743,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"storageInGB": m1,
|
||||
"storageLimitExceeded":
|
||||
MessageLookupByLibrary.simpleMessage("Limite de stockage atteinte"),
|
||||
"storageUsageInfo": m65,
|
||||
"storageUsageInfo": m66,
|
||||
"strongStrength": MessageLookupByLibrary.simpleMessage("Forte"),
|
||||
"subAlreadyLinkedErrMessage": m66,
|
||||
"subWillBeCancelledOn": m67,
|
||||
"subAlreadyLinkedErrMessage": m67,
|
||||
"subWillBeCancelledOn": m68,
|
||||
"subscribe": MessageLookupByLibrary.simpleMessage("S\'abonner"),
|
||||
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
|
||||
"Vous avez besoin d\'un abonnement payant actif pour activer le partage."),
|
||||
@@ -1758,7 +1763,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"suggestFeatures": MessageLookupByLibrary.simpleMessage(
|
||||
"Suggérer des fonctionnalités"),
|
||||
"support": MessageLookupByLibrary.simpleMessage("Support"),
|
||||
"syncProgress": m68,
|
||||
"syncProgress": m69,
|
||||
"syncStopped":
|
||||
MessageLookupByLibrary.simpleMessage("Synchronisation arrêtée ?"),
|
||||
"syncing": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1771,7 +1776,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Appuyer pour déverrouiller"),
|
||||
"tapToUpload":
|
||||
MessageLookupByLibrary.simpleMessage("Appuyer pour envoyer"),
|
||||
"tapToUploadIsIgnoredDue": m69,
|
||||
"tapToUploadIsIgnoredDue": m70,
|
||||
"tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage(
|
||||
"Il semble qu\'une erreur s\'est produite. Veuillez réessayer après un certain temps. Si l\'erreur persiste, veuillez contacter notre équipe d\'assistance."),
|
||||
"terminate": MessageLookupByLibrary.simpleMessage("Se déconnecter"),
|
||||
@@ -1787,7 +1792,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Le téléchargement n\'a pas pu être terminé"),
|
||||
"theLinkYouAreTryingToAccessHasExpired":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"The link you are trying to access has expired."),
|
||||
"Le lien que vous essayez d\'accéder a expiré."),
|
||||
"theRecoveryKeyYouEnteredIsIncorrect":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"La clé de récupération que vous avez entrée est incorrecte"),
|
||||
@@ -1795,7 +1800,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"theseItemsWillBeDeletedFromYourDevice":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"Ces éléments seront supprimés de votre appareil."),
|
||||
"theyAlsoGetXGb": m70,
|
||||
"theyAlsoGetXGb": m71,
|
||||
"theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage(
|
||||
"Ils seront supprimés de tous les albums."),
|
||||
"thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1811,7 +1816,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Cette adresse mail est déjà utilisé"),
|
||||
"thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage(
|
||||
"Cette image n\'a pas de données exif"),
|
||||
"thisIsPersonVerificationId": m71,
|
||||
"thisIsPersonVerificationId": m72,
|
||||
"thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage(
|
||||
"Ceci est votre ID de vérification"),
|
||||
"thisWillLogYouOutOfTheFollowingDevice":
|
||||
@@ -1835,7 +1840,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"total": MessageLookupByLibrary.simpleMessage("total"),
|
||||
"totalSize": MessageLookupByLibrary.simpleMessage("Taille totale"),
|
||||
"trash": MessageLookupByLibrary.simpleMessage("Corbeille"),
|
||||
"trashDaysLeft": m72,
|
||||
"trashDaysLeft": m73,
|
||||
"trim": MessageLookupByLibrary.simpleMessage("Recadrer"),
|
||||
"tryAgain": MessageLookupByLibrary.simpleMessage("Réessayer"),
|
||||
"turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1856,7 +1861,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"L\'authentification à deux facteurs a été réinitialisée avec succès "),
|
||||
"twofactorSetup": MessageLookupByLibrary.simpleMessage(
|
||||
"Configuration de l\'authentification à deux facteurs"),
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m73,
|
||||
"typeOfGallerGallerytypeIsNotSupportedForRename": m74,
|
||||
"unarchive": MessageLookupByLibrary.simpleMessage("Désarchiver"),
|
||||
"unarchiveAlbum":
|
||||
MessageLookupByLibrary.simpleMessage("Désarchiver l\'album"),
|
||||
@@ -1884,10 +1889,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"updatingFolderSelection": MessageLookupByLibrary.simpleMessage(
|
||||
"Mise à jour de la sélection du dossier..."),
|
||||
"upgrade": MessageLookupByLibrary.simpleMessage("Améliorer"),
|
||||
"uploadIsIgnoredDueToIgnorereason": m74,
|
||||
"uploadIsIgnoredDueToIgnorereason": m75,
|
||||
"uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
|
||||
"Envoi des fichiers vers l\'album..."),
|
||||
"uploadingMultipleMemories": m75,
|
||||
"uploadingMultipleMemories": m76,
|
||||
"uploadingSingleMemory":
|
||||
MessageLookupByLibrary.simpleMessage("Sauvegarde 1 souvenir..."),
|
||||
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1902,8 +1907,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
MessageLookupByLibrary.simpleMessage("Utiliser la clé de secours"),
|
||||
"useSelectedPhoto": MessageLookupByLibrary.simpleMessage(
|
||||
"Utiliser la photo sélectionnée"),
|
||||
"usedSpace": MessageLookupByLibrary.simpleMessage("Mémoire utilisée"),
|
||||
"validTill": m76,
|
||||
"usedSpace": MessageLookupByLibrary.simpleMessage("Stockage utilisé"),
|
||||
"validTill": m77,
|
||||
"verificationFailedPleaseTryAgain":
|
||||
MessageLookupByLibrary.simpleMessage(
|
||||
"La vérification a échouée, veuillez réessayer"),
|
||||
@@ -1912,7 +1917,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"verify": MessageLookupByLibrary.simpleMessage("Vérifier"),
|
||||
"verifyEmail":
|
||||
MessageLookupByLibrary.simpleMessage("Vérifier l\'email"),
|
||||
"verifyEmailID": m77,
|
||||
"verifyEmailID": m78,
|
||||
"verifyIDLabel": MessageLookupByLibrary.simpleMessage("Vérifier"),
|
||||
"verifyPasskey":
|
||||
MessageLookupByLibrary.simpleMessage("Vérifier le code d\'accès"),
|
||||
@@ -1941,7 +1946,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"viewRecoveryKey":
|
||||
MessageLookupByLibrary.simpleMessage("Voir la clé de récupération"),
|
||||
"viewer": MessageLookupByLibrary.simpleMessage("Observateur"),
|
||||
"viewersSuccessfullyAdded": m78,
|
||||
"viewersSuccessfullyAdded": m79,
|
||||
"visitWebToManage": MessageLookupByLibrary.simpleMessage(
|
||||
"Veuillez visiter web.ente.io pour gérer votre abonnement"),
|
||||
"waitingForVerification": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1959,7 +1964,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"whatsNew": MessageLookupByLibrary.simpleMessage("Nouveautés"),
|
||||
"yearShort": MessageLookupByLibrary.simpleMessage("an"),
|
||||
"yearly": MessageLookupByLibrary.simpleMessage("Annuel"),
|
||||
"yearsAgo": m79,
|
||||
"yearsAgo": m80,
|
||||
"yes": MessageLookupByLibrary.simpleMessage("Oui"),
|
||||
"yesCancel": MessageLookupByLibrary.simpleMessage("Oui, annuler"),
|
||||
"yesConvertToViewer": MessageLookupByLibrary.simpleMessage(
|
||||
@@ -1992,7 +1997,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"Vous ne pouvez pas partager avec vous-même"),
|
||||
"youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage(
|
||||
"Vous n\'avez aucun élément archivé."),
|
||||
"youHaveSuccessfullyFreedUp": m80,
|
||||
"youHaveSuccessfullyFreedUp": m81,
|
||||
"yourAccountHasBeenDeleted":
|
||||
MessageLookupByLibrary.simpleMessage("Votre compte a été supprimé"),
|
||||
"yourMap": MessageLookupByLibrary.simpleMessage("Votre carte"),
|
||||
|
||||