Merge branch 'main' into remote_db

This commit is contained in:
Neeraj Gupta
2025-06-09 13:15:33 +05:30
396 changed files with 11194 additions and 6102 deletions

View File

@@ -1,24 +1,21 @@
name: Report a bug
description: Let us know if something's not working the way you expected.
description: For regressions only (things that were working earlier)
labels: []
body:
- type: markdown
attributes:
value: |
Before opening a new bug report, please ensure
1. you are on the latest version (it might've already been fixed),
2. you've searched for existing issues (please add your observations as a comment there instead of creating a duplicate).
If you are self hosting, please create a community [Q&A](https://github.com/ente-io/ente/discussions/categories/q-a) instead.
Before opening a new issue, **please** ensure
1. You are on the latest version,
2. You've searched for existing issues,
3. It was working earlier (otherwise use [this](https://github.com/ente-io/ente/discussions/categories/enhancements))
4. It is not about self hosting (otherwise use [this](https://github.com/ente-io/ente/discussions/categories/q-a))
- type: textarea
attributes:
label: Description
description: >
Please describe the bug. If possible, also include the steps to
reproduce the behaviour, and the expected behaviour (sometimes
bugs are just expectation mismatches, in which case this would be
a good fit for
[enhancements](https://github.com/ente-io/ente/discussions/categories/enhancements)).
Describe the bug and steps to reproduce the behaviour, and how it
differs from the previously working behaviour.
validations:
required: true
- type: input
@@ -30,15 +27,12 @@ body:
attributes:
label: Last working version
description: >
The version where the feature was last known to be working. It is
fine if you don't remember the exact version (mention roughly
then), but if there just isn't a last known working version, then
it is likely that what is being reported is not an issue
(regression) but an enhancement. The difference between the two
categories is not just semantic - **enhancements use GitHub
discussions and so can be [upvoted by the
community](https://github.com/ente-io/ente/discussions/categories/enhancements)**
(while issues cannot be).
The version where things were last known to be working. It is fine
if you don't remember the exact version (mention roughly then),
but **if there just isn't a last working version, then please file
it as an
[enhancement](https://github.com/ente-io/ente/discussions/categories/enhancements))**
(where the community upvotes can be used to help prioritize).
placeholder: e.g. v1.2.3
- type: dropdown
attributes:

View File

@@ -475,6 +475,16 @@
"title": "FreeTaxUSA",
"slug": "freetaxusa"
},
{
"title": "FZJ",
"slug": "fzj",
"hex": "023d6b",
"altNames": [
"Forschungszentrum Jülich",
"FZJ IdP",
"iffLogin"
]
},
{
"title": "G2A"
},
@@ -739,8 +749,8 @@
]
},
{
"title": "Mercado Livre",
"slug": "mercado_livre",
"title": "Mercado Libre",
"slug": "mercado_libre",
"altNames": [
"Mercado Libre",
"MercadoLibre",
@@ -1020,6 +1030,14 @@
"qiniu"
]
},
{
"title": "R10.net",
"slug": "r10",
"altNames": [
"R10",
"r10.net"
]
},
{
"title": "Raindrop.io",
"slug": "raindrop_io",
@@ -1399,6 +1417,25 @@
},
{
"title": "CoinSpot"
},
{
"title": "Aternos",
"slug": "aternos"
},
{
"title": "Toshl Finance",
"slug": "toshl_finance",
"altNames": [
"Toshl"
]
},
{
"title": "xAI",
"slug": "xai"
},
{
"title": "Cronometer",
"slug": "cronometer"
}
]
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xml:space="preserve"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300mm" height="300mm" viewBox="0 0 16000 16000" preserveAspectRatio="xMidYMid meet">
<g>
<path fill="#2b87d3" d="M0 8000 l0 -8000 8000 0 8000 0 0 8000 0 8000 -8000 0 -8000 0 0 -8000z m13990 0 l0 -5990 -5990 0 -5990 0 0 5990 0 5990 5990 0 5990 0 0 -5990z"/>
<path fill="#2b87d3" d="M2995 12998 c-3 -7 -4 -911 -3 -2008 l3 -1995 1005 0 1005 0 3 997 2 998 998 2 997 3 0 1005 0 1005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
<path fill="#2b87d3" d="M8995 12998 c-3 -7 -4 -461 -3 -1008 l3 -995 997 -3 998 -2 2 -998 3 -997 1005 0 1005 0 0 2005 0 2005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
<path fill="#2b87d3" d="M5995 9998 c-3 -7 -4 -911 -3 -2008 l3 -1995 2005 0 2005 0 0 2005 0 2005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
<path fill="#2b87d3" d="M2995 6998 c-3 -7 -4 -911 -3 -2008 l3 -1995 2005 0 2005 0 0 1005 0 1005 -997 3 -998 2 -2 998 -3 997 -1003 3 c-797 2 -1004 0 -1007 -10z"/>
<path fill="#2b87d3" d="M10997 7003 c-4 -3 -7 -453 -7 -1000 l0 -993 -997 -2 -998 -3 0 -1005 0 -1005 2005 0 2005 0 0 2005 0 2005 -1001 3 c-550 1 -1004 -1 -1007 -5z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="107.51429"
height="102.75398"
viewBox="0 0 107.51429 102.75398"
fill="none"
version="1.1"
id="svg12"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs12" />
<g
id="g12"
transform="translate(34.950067,-215.99315)">
<path
d="m 57.85843,271.77769 c -0.774,-0.1257 -1.5665,0.0595 -2.2048,0.5152 -0.6382,0.4557 -1.0704,1.145 -1.2028,1.9179 -0.1447,0.8786 -0.3109,1.7775 -0.496,2.6697 -1.7799,8.5172 -5.1174,16.6325 -9.8446,23.9376 -5.0162,7.5169 -10.8489,12.0047 -15.6015,12.0047 -2.2725,0.0492 -4.5367,-0.2938 -6.6926,-1.0138 -5.6912,-1.775 -11.7884,-1.7717 -17.4777,0.009 -2.1479,0.7139 -4.4023,1.0536 -6.6652,1.0044 -4.7486,0 -10.58,-4.4851 -15.5963,-11.9979 -4.727,-7.3046 -8.0652,-15.4188 -9.84715,-23.935 -1.97756,-9.485 -1.60717,-17.8346 1.06924,-24.1485 1.09829,-2.7795 2.74951,-5.307 4.85381,-7.4292 2.1042,-2.1223 4.6176,-3.7952 7.3876,-4.9171 2.77,-1.122 5.7387,-1.6698 8.7268,-1.6101 2.988,0.0597 5.9328,0.7255 8.6558,1.9572 l 0.1663,0.0635 c 3.1372,1.3741 6.5201,2.0997 9.9449,2.1331 h 0.127 c 3.4272,-0.0346 6.8117,-0.763 9.9498,-2.1412 l 0.1502,-0.0581 c 2.981,-1.3116 6.2053,-1.979 9.462,-1.9586 1.8593,0.0219 3.7051,0.317 5.4785,0.8759 1.02,0.3144 2.0172,0.6982 2.9846,1.1489 0.7121,0.333 1.5269,0.3705 2.2665,0.1042 0.7397,-0.2662 1.3439,-0.8144 1.6805,-1.5248 0.1645,-0.3511 0.2583,-0.7312 0.2755,-1.1185 0.0172,-0.3873 -0.0421,-0.7742 -0.1749,-1.1385 -0.1328,-0.3643 -0.3362,-0.6987 -0.5986,-0.9841 -0.2625,-0.2854 -0.5788,-0.5161 -0.9307,-0.6789 -1.2159,-0.5667 -2.4693,-1.0495 -3.7512,-1.445 -2.3402,-0.7366 -4.7769,-1.1202 -7.2302,-1.1381 -3.9898,-0.0245 -7.9413,0.7777 -11.6058,2.356 l -0.1514,0.0581 c -1.5551,0.6563 -3.1723,1.1544 -4.8271,1.4869 -0.2018,-1.6639 -0.5606,-3.305 -1.0719,-4.9013 11.4383,-2.0749 11.0342,-15.88816 11.0342,-15.88816 -9.3133,1.40309 -13.3294,4.93919 -14.9623,8.33877 -1.5519,-2.01717 -3.4478,-3.74459 -5.6002,-5.10275 -0.3906,-0.24958 -0.8265,-0.41979 -1.2828,-0.50093 -0.4563,-0.0811 -0.9238,-0.0716 -1.3765,0.0281 -0.4526,0.0997 -0.8812,0.28749 -1.2613,0.55278 -0.38,0.26529 -0.7041,0.60283 -0.9537,0.99335 -0.2496,0.39052 -0.4198,0.82638 -0.5009,1.28269 -0.0812,0.45631 -0.0717,0.92412 0.028,1.37675 0.0997,0.45262 0.2875,0.88118 0.5528,1.2612 0.2653,0.38003 0.6028,0.70409 0.9933,0.95367 1.9269,1.29176 3.5632,2.97116 4.8046,4.93076 1.2415,1.9597 2.0607,4.1567 2.4055,6.4507 -1.2634,-0.3369 -2.5038,-0.7536 -3.7143,-1.2476 l -0.1759,-0.0676 c -3.6617,-1.5742 -7.6092,-2.3745 -11.5949,-2.3506 -9.0309,0 -20.73562,6.0327 -25.63155,17.5723 -3.97541,9.3756 -2.962867,20.2042 -1.41244,27.6387 1.94038,9.2532 5.57254,18.0686 10.71379,26.003 6.3017,9.4378 13.5982,14.6369 20.5461,14.6369 2.8438,0.049 5.6759,-0.375 8.3806,-1.255 4.5889,-1.454 9.515,-1.454 14.1039,0 2.6964,0.878 5.5198,1.3 8.3549,1.251 6.8478,0 14.3376,-5.3384 20.5461,-14.645 5.1404,-7.9344 8.7713,-16.75 10.7098,-26.0031 0.2041,-0.9746 0.3851,-1.9559 0.5406,-2.917 0.062,-0.3835 0.0481,-0.7755 -0.0416,-1.1535 -0.0897,-0.3779 -0.2534,-0.7345 -0.4812,-1.0492 -0.2278,-0.3147 -0.5154,-0.5813 -0.8464,-0.7846 -0.3311,-0.2033 -0.6991,-0.3393 -1.0828,-0.4001"
fill="#ff6733"
id="path11" />
<path
d="m 71.33493,256.39499 v 0.0121 l -8.2649,-5.8475 -0.4271,-0.2298 0.0512,-0.5177 0.2393,-10.0798 h 0.0095 c 0.0046,-0.5208 -0.1263,-1.0339 -0.3802,-1.4887 -0.2538,-0.4548 -0.6219,-0.8357 -1.0675,-1.1053 -0.4471,-0.2723 -0.9569,-0.4242 -1.4801,-0.441 -0.5233,-0.0169 -1.0422,0.102 -1.5059,0.3451 l -8.7538,4.3593 c -1.2619,0.6789 -2.3372,1.6578 -3.1318,2.8502 -0.7946,1.1925 -1.2839,2.5621 -1.4247,3.9881 l -0.7408,7.7913 -6.3059,3.3658 c -2.4638,-3.7027 -5.7171,-6.8136 -9.5264,-9.1092 -3.8093,-2.2956 -8.0799,-3.7189 -12.5047,-4.1675 -4.4249,-0.4486 -8.894,0.0886 -13.0866,1.573 -4.1925,1.4843 -8.0041,3.879 -11.1609,7.0119 -2.7859,2.7623 -4.9974,6.0489 -6.5065,9.6702 -1.509,3.6213 -2.286,7.5057 -2.286,11.4288 0,3.9232 0.777,7.8075 2.286,11.4288 1.5091,3.6214 3.7206,6.908 6.5065,9.6702 4.197,4.1711 9.5317,7.0104 15.3356,8.1623 5.8039,1.1518 11.8188,0.5648 17.2905,-1.6873 5.4718,-2.2522 10.157,-6.0694 13.4684,-10.9731 3.3114,-4.9037 5.1017,-10.6758 5.1465,-16.5928 0.0013,-3.8327 -0.7435,-7.6291 -2.1926,-11.1774 l 6.2181,-3.3117 -0.0109,0.0311 6.7206,4.0741 c 1.2315,0.7427 2.6257,1.1736 4.0615,1.2554 1.4357,0.0817 2.8695,-0.1882 4.1772,-0.7863 l 8.7534,-4.3593 c 0.472,-0.2216 0.877,-0.5622 1.177,-0.9882 0.3,-0.426 0.483,-0.9228 0.532,-1.4413 0.049,-0.5185 -0.038,-1.0409 -0.252,-1.5156 -0.214,-0.4747 -0.548,-0.8853 -0.97,-1.1915 m -34.1485,19.4107 c -0.0347,4.7475 -1.47,9.3791 -4.1262,13.3141 -2.6561,3.9351 -6.4148,6.9984 -10.8048,8.806 -4.39,1.8076 -9.2158,2.2789 -13.8727,1.355 -4.6568,-0.924 -8.9373,-3.2021 -12.3047,-6.5488 -2.2347,-2.2161 -4.0086,-4.8527 -5.2191,-7.7579 -1.2105,-2.9051 -1.8335,-6.0211 -1.8335,-9.1684 0,-3.1472 0.623,-6.2632 1.8335,-9.1684 1.2105,-2.9051 2.9844,-5.5417 5.2191,-7.7578 2.5007,-2.4815 5.514,-4.3861 8.8284,-5.5801 3.3145,-1.194 6.85,-1.6486 10.3587,-1.3319 3.5087,0.3167 6.9057,1.397 9.9528,3.1652 3.0471,1.7682 5.6703,4.1815 7.6862,7.0707 l -8.1614,4.3552 c -1.3138,-1.6306 -2.9557,-2.9671 -4.8188,-3.9229 -1.8632,-0.9559 -3.9062,-1.5098 -5.997,-1.6259 -2.0908,-0.1161 -4.1826,0.2082 -6.1401,0.952 -1.9576,0.7437 -3.7372,1.8903 -5.2234,3.3654 -1.3826,1.3719 -2.4797,3.0038 -3.2285,4.8018 -0.7489,1.798 -1.1343,3.7264 -1.1343,5.674 0,1.9477 0.3854,3.8761 1.1343,5.6741 0.7488,1.7979 1.8459,3.4299 3.2285,4.8018 2.317,2.3457 5.3482,3.8548 8.6165,4.2898 3.2684,0.4351 6.5886,-0.2286 9.4386,-1.8865 2.85,-1.6579 5.0682,-4.2161 6.3055,-7.2723 1.2374,-3.0562 1.4238,-6.437 0.53,-9.6107 l 8.2064,-4.3687 c 1.0104,2.6766 1.5273,5.5142 1.526,8.3752 z m -26.0801,1.2734 c 0.3553,0.6991 0.9725,1.2295 1.7171,1.4759 0.7445,0.2463 1.5564,0.1885 2.2586,-0.1607 l 6.9193,-3.6848 c 0.2696,2.1001 -0.213,4.2278 -1.3623,6.006 -1.1493,1.7782 -2.891,3.0921 -4.9165,3.7087 -2.0255,0.6167 -4.2042,0.4963 -6.1494,-0.3399 -1.9452,-0.8361 -3.5316,-2.334 -4.4779,-4.228 -0.9463,-1.8941 -1.1912,-4.0621 -0.6917,-6.1196 0.4995,-2.0576 1.7111,-3.8718 3.4205,-5.1212 1.7094,-1.2494 3.806,-1.8532 5.9181,-1.7044 2.1121,0.1487 4.1034,1.0405 5.6207,2.5172 l -6.9345,3.6997 c -0.3472,0.1714 -0.6571,0.4099 -0.9118,0.7018 -0.2546,0.2918 -0.449,0.6311 -0.5719,0.9984 -0.1229,0.3673 -0.172,0.7553 -0.1442,1.1416 0.0278,0.3863 0.1317,0.7633 0.3059,1.1093 z m 39.7718,-27.0764 c 0.219,-1.157 0.3879,-2.1708 1.4557,-2.7467 l 4.5109,-2.2438 -0.6085,5.5191 -5.7029,2.8386 z m 8.0467,10.3339 c -0.5151,0.173 -1.0642,0.2192 -1.6009,0.1347 -0.5367,-0.0845 -1.0453,-0.2971 -1.4824,-0.6199 l -2.9222,-1.7424 5.7042,-2.8386 4.8109,2.8224 z"
fill="#ff6733"
id="path12" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="21.087mm" height="21.735mm" version="1.1" viewBox="0 0 21.087 21.735" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(34.396 -25.136)">
<path d="m-26.575 34.404 3.5438-9.2136c-0.38165-0.0363-0.76329-0.0545-1.1631-0.0545-1.9082 0-3.7983 0.49067-5.452 1.4538-1.6538 0.96316-3.035 2.3261-3.9982 3.998-0.09087 0.14538-0.16356 0.30893-0.25443 0.47249v0.0545l-0.14538 0.39981c-1.0904 2.7804 0.56337 4.834 2.1626 5.4337 0.45433 0.16355 0.92685 0.25441 1.3994 0.25441 0.72694 0 1.4175-0.18172 2.0536-0.54517 0.6179-0.36347 1.1449-0.8723 1.5084-1.4902 0.14539-0.23624 0.25443-0.49067 0.34529-0.76326m6.4334 2.3625c-2.1081 5.5246-7.2149 7.5962-11.885 6.7966 0.70877 0.72692 1.5084 1.3448 2.3807 1.8536 1.6538 0.96316 3.5257 1.4538 5.452 1.4538 1.9082 0 3.7619-0.49067 5.4339-1.4538 1.6538-0.96316 3.035-2.3261 3.9982-3.9798 0.94502-1.6537 1.4539-3.5437 1.4539-5.4337 0-1.9082-0.50886-3.8163-1.4539-5.4518-0.5452-0.94499-1.1994-1.7628-1.9809-2.4897l-3.3984 8.7048" fill="#023d6b" fill-rule="evenodd" stroke-width=".26458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 280.6 100" style="enable-background:new 0 0 280.6 100;" xml:space="preserve">
<style type="text/css">
.st0{enable-background:new ;}
.st1{fill:#FFFFFF;}
.st2{fill:#3F4257;}
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#3F4257;}
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:#EA4335;}
.st5{fill:#EA4335;}
.st6{fill:#FBBC05;}
.st7{fill:#4285F4;}
.st8{fill:#34A853;}
</style>
<g>
<g class="st0">
<path class="st1" d="M114.5,57.9h-6.3L97.9,46.2h-7.2v11.7h-5.4V24.1c5.3,0,10.5,0,15.8,0c7.8,0,12,5.4,12,11
c0,4.8-2.5,9.5-9.2,10.5L114,57L114.5,57.9z M90.7,29.1v12.2H101c4.5,0,6.5-2.8,6.5-6c0-3-2-6.2-6.4-6.2H90.7z"/>
<polygon class="st1" points="124.5,57.9 129.9,57.9 129.9,24.2 118.7,27.7 118.7,32.4 124.5,30.8 "/>
<path class="st1" d="M139.7,38.1c0-19.6,28.4-19.5,28.4,0V44c0,19.5-28.4,19.6-28.4,0V38.1z M145.2,44c0,12.8,17.5,12.8,17.5,0
v-5.9c0-12.6-17.5-12.8-17.5,0V44z"/>
</g>
<g class="st0">
<path class="st1" d="M188.8,24.2l22.8,27.9V24.2h2.6v33.8h-1L190.3,30v27.9h-2.6V24.2H188.8z"/>
<path class="st1" d="M226.3,39.7h19.8v2.4h-19.8v13.3h21.4v2.5h-24V24.2h23.4v2.5h-20.8V39.7z"/>
<path class="st1" d="M263.3,26.5h-12.2v-2.3h27.1v2.3h-12.2v31.4h-2.6V26.5z"/>
</g>
<g id="Group_174_3_" transform="translate(-5071.999 -1624)">
<path class="st1" d="M5106.8,1722c-5.8,0-11.4-1.2-16.4-3.6c-4.9-2.3-9.1-5.7-12-9.8c-5-7.1-6.1-16-3-25l0-0.1
c7.8-22.5,13-25.7,18.7-27.4l0.5-0.1l0,0.2l0.8-0.1c0.5,0,0.9-0.1,1.4-0.1c0.6,0,1.2,0,1.8,0.1l0.8,0.1l0.4,0.1
c1.9,0.3,3.8,0.8,5.7,1.5l0,0l0,0c0.4,0.1,0.9,0.2,1.3,0.2c0,0,0,0,0,0c5.2,0,12.8-10.1,18.8-28.9c-0.3,1.7-1.1,6.5-1.4,8.1
c-1.2,6.5-2.3,12.7-3.2,17.7l-0.1,0.5l0.5,0.2c2.8,1.2,5,3.8,6.5,7.6l0.1,0.3l0.3,0.1c5.2,1.9,9.3,4.6,12.1,8
c2.5,3,4.2,6.5,4.8,10.5c0.9,5.3,0,11.6-2.5,17.6c-2.2,5.2-5.3,9.8-8.5,12.3C5126,1718.6,5116.6,1722,5106.8,1722L5106.8,1722z
M5094.2,1635.7l0.5,2.5l-0.2,16.8L5094.2,1635.7z M5099.2,1654.4c-0.2-8.7,5.1-17.6,12.8-21.7c-0.7,0.9-1.4,1.8-2,2.7
C5103.8,1639,5100.3,1645.3,5099.2,1654.4z"/>
<g>
<path class="st1" d="M5128.3,1663.1c-1.7-4.1-4-6.7-6.9-8c1.5-8.3,3.6-20,5.7-31.1c-0.4,1.7-1.1,3.4-1.7,5
c-1.7,9.3-4.2,20.4-5.5,27.4c3.2,1.5,5.6,4.2,6.9,7.6c27.3,9.9,16.5,38.3,6.9,46.1c-28,22.7-67.7,4-57.3-26.9
c6.8-20.3,12.9-24.2,18.7-26v0c2.6-0.5,6.9,0.4,9.2,0.9c1.4,0.4,2.2,0.5,2.5,0.5c8,0.5,15.6-18.5,18.5-29.8
c-2.1,5.3-11.8,30.7-19.9,28.3c-1.9-0.7-3.8-1.2-5.8-1.6c0.8-8.8,4-15.8,10.7-19.6c1.2-1.8,2.7-3.6,4.2-5.1
c-9.9,3.5-16.8,14.4-16.1,24.6c-1.1-0.1-2.2-0.1-3.3,0l0.2-17.2l-2.1-9.6l0.5,26.8c-6.3,1.9-11.6,5.9-19.1,27.8l0,0
c-7.8,22.6,10.4,39.4,32.1,39.4c9.2,0,19-3.1,27.7-10C5144.6,1704.6,5157.1,1673.7,5128.3,1663.1z"/>
<polygon class="st2" points="5114.7,1630.8 5114.7,1630.8 5114.7,1630.8 "/>
</g>
<g id="Group_1_3_" transform="translate(5072 1624)">
<path id="Path_839_2_" class="st3" d="M33.1,41.9c1.8-4.6,5.7-7.7,10.2-7.7c6.2,0,11.3,6.1,11.3,13.6s-5.1,13.6-11.3,13.6
c-4.5,0-8.4-3.2-10.2-7.8c0.1-0.2,0.2-0.3,0.3-0.5c1.7,4.2,5.2,7.2,9.4,7.2c5.7,0,10.4-5.6,10.4-12.4s-4.6-12.4-10.4-12.4
c-4.1,0-7.7,2.9-9.4,7.1C33.3,42.2,33.2,42.1,33.1,41.9z M25.1,36.4c2.9,0,5.5,1.9,7.1,4.8c-1.2-2.3-3.6-3.7-6.2-3.8
c-4.4,0-7.9,4.6-7.9,10.3S22.5,58,26,58s4.7-1.9,6.1-4.2c-1.6,2.8-3.2,5.1-7,5.1c-3.8,0-8.7-5-8.7-11.2
C16.4,41.5,20.3,36.4,25.1,36.4L25.1,36.4z"/>
<path id="Path_840_2_" class="st3" d="M27.8,45.5c0.9,0,1.6-0.7,1.6-1.5c0-0.9-0.7-1.6-1.5-1.6c-0.9,0-1.6,0.7-1.6,1.5
c0,0,0,0,0,0C26.3,44.8,27,45.4,27.8,45.5z"/>
<path id="Path_842_2_" class="st3" d="M46.9,46.4c0.9,0,1.6-0.6,1.6-1.5c0-0.9-0.6-1.6-1.5-1.6s-1.6,0.6-1.6,1.5c0,0,0,0,0,0.1
C45.4,45.7,46.1,46.4,46.9,46.4z"/>
<path id="Path_843_2_" class="st3" d="M26.1,62.8l0.6,0.1l-2.3,23.4l-0.6-0.1L26.1,62.8z"/>
<path id="Path_844_2_" class="st3" d="M38.6,65.3l0.6,0l1.5,23.5l-0.6,0L38.6,65.3z"/>
<path id="Path_845_2_" class="st3" d="M51.6,64.2l0.5-0.1l3.1,19.7L54.6,84L51.6,64.2z"/>
<g>
<polygon class="st2" points="8.1,57.5 8.1,57.5 8.1,57.5 8.1,57.5 "/>
<path class="st2" d="M8.1,57.5c-4.3,39.6,64.1,47.3,56.7-0.1c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c-0.3,0.2-0.6,0.4-0.9,0.6
c-0.3,0.2-0.6,0.5-1,0.7c-0.9,0.6-2.2,1.2-3.8,1.9C41.4,66.9,17.2,62.2,8.1,57.5 M9.3,58.9c7.6,7,37.3,10.1,53,2.1
c0,0,0.3-0.1,0.5-0.3C67.3,101.9,5.8,94.4,9.3,58.9z"/>
</g>
</g>
</g>
<circle class="st1" cx="177.1" cy="53.6" r="4"/>
<path id="Forma_1_3_" class="st4" d="M153,74.7c-1,0-2.1,0.3-2.9,1c-0.7,0.6-1.3,1.3-1.7,2c-0.4-0.8-1-1.5-1.7-2
c-0.8-0.7-1.8-1-2.9-1c-2.9,0-5.2,2.4-5.2,5.6c0,3.5,2.8,5.8,7,9.4c0.7,0.6,1.5,1.3,2.4,2c0.2,0.2,0.6,0.2,0.8,0
c0.8-0.7,1.6-1.4,2.4-2c4.2-3.6,7-6,7-9.4C158.1,77.1,155.9,74.7,153,74.7z"/>
<g>
<path class="st1" d="M86.6,71.9l5.2,15.7l5.3-15.7h1.5l5.3,15.7l5.2-15.7h2.7l-6.6,19h-2.4l-4.9-14.2L93,90.9h-2.4l-6.6-19H86.6z"
/>
<path class="st1" d="M125.1,88.7c-1.4,1.6-3.7,2.4-5.8,2.4c-4.1,0-7-2.7-7-7c0-4,2.9-6.9,6.9-6.9c4.1,0,7.2,2.5,6.7,7.9h-11.4
c0.2,2.5,2.3,4,4.9,4c1.5,0,3.4-0.6,4.3-1.7L125.1,88.7L125.1,88.7z M123.8,83.2c-0.1-2.6-1.9-4-4.5-4c-2.3,0-4.4,1.4-4.7,4
L123.8,83.2L123.8,83.2z"/>
</g>
<g>
<path class="st5" d="M214.6,84.2c0,4.8-3.7,8.3-8.3,8.3s-8.3-3.5-8.3-8.3c0-4.8,3.7-8.3,8.3-8.3S214.6,79.4,214.6,84.2z M211,84.2
c0-3-2.2-5-4.7-5s-4.7,2-4.7,5c0,3,2.2,5,4.7,5S211,87.2,211,84.2z"/>
<path class="st6" d="M232.6,84.2c0,4.8-3.7,8.3-8.3,8.3s-8.3-3.5-8.3-8.3c0-4.8,3.7-8.3,8.3-8.3S232.6,79.4,232.6,84.2z M229,84.2
c0-3-2.2-5-4.7-5s-4.7,2-4.7,5c0,3,2.2,5,4.7,5S229,87.2,229,84.2z"/>
<path class="st7" d="M249.9,76.4v14.9c0,6.1-3.6,8.7-7.9,8.7c-4,0-6.5-2.7-7.4-4.9l3.2-1.3c0.6,1.4,2,3,4.2,3
c2.7,0,4.4-1.7,4.4-4.9v-1.2h-0.1c-0.8,1-2.4,1.9-4.4,1.9c-4.2,0-8-3.6-8-8.3c0-4.7,3.8-8.3,8-8.3c2,0,3.6,0.9,4.4,1.9h0.1v-1.4
L249.9,76.4L249.9,76.4z M246.7,84.3c0-2.9-2-5.1-4.4-5.1c-2.5,0-4.6,2.1-4.6,5.1c0,2.9,2.1,5,4.6,5
C244.7,89.3,246.7,87.2,246.7,84.3z"/>
<path class="st8" d="M255.6,67.7V92H252V67.7H255.6z"/>
<path class="st5" d="M269.5,87l2.8,1.9c-0.9,1.4-3.1,3.7-6.9,3.7c-4.7,0-8.3-3.7-8.3-8.3c0-4.9,3.6-8.3,7.8-8.3
c4.3,0,6.4,3.4,7.1,5.3l0.4,0.9l-11.1,4.6c0.9,1.7,2.2,2.5,4,2.5C267.3,89.3,268.6,88.4,269.5,87L269.5,87z M260.8,84l7.4-3.1
c-0.4-1-1.6-1.8-3.1-1.8C263.3,79.1,260.7,80.8,260.8,84z"/>
<path class="st7" d="M184.5,82.1v-3.5h11.9c0.1,0.6,0.2,1.3,0.2,2.1c0,2.6-0.7,5.9-3.1,8.3c-2.3,2.4-5.2,3.6-9,3.6
c-7.1,0-13.1-5.8-13.1-12.9c0-7.1,6-12.9,13.1-12.9c3.9,0,6.7,1.5,8.9,3.6l-2.5,2.5c-1.5-1.4-3.6-2.5-6.4-2.5
c-5.2,0-9.3,4.2-9.3,9.4c0,5.2,4.1,9.4,9.3,9.4c3.4,0,5.3-1.4,6.5-2.6c1-1,1.7-2.4,1.9-4.4L184.5,82.1z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="katman_1"
x="0px"
y="0px"
viewBox="0 0 841.89 595.28"
style="enable-background:new 0 0 841.89 595.28;"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs4" />
<g
id="g4"
style="fill:currentColor">
<polygon
points="557.09,211.99 565.4,538.36 631.96,538.36 640.28,93.18"
id="polygon1"
style="fill:currentColor" />
<polygon
points="640.28,56.91 538.72,56.91 379.35,284.53 430.13,357.05"
id="polygon2"
style="fill:currentColor" />
<polygon
points="201.61,538.36 303.17,538.36 353.96,465.84 303.17,393.31"
id="polygon3"
style="fill:currentColor" />
<polygon
points="201.61,211.99 430.13,538.36 531.69,538.36 303.17,211.99"
id="polygon4"
style="fill:currentColor" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -8,7 +8,7 @@
},
"onBoardingBody": "Бяспечна зрабіць рэзервовую копію кодаў 2ФА",
"onBoardingGetStarted": "Пачаць",
"setupFirstAccount": "Наладзіць ваш першы ўліковы запіс",
"setupFirstAccount": "Наладзіць свой першы ўліковы запіс",
"importScanQrCode": "Сканіраваць код QR-код",
"qrCode": "QR-код",
"importEnterSetupKey": "Увесці ключ наладжвання",
@@ -45,19 +45,38 @@
"timeBasedKeyType": "Заснаваныя на часе (TOTP)",
"counterBasedKeyType": "Заснаваныя на лічыльніку (HOTP)",
"saveAction": "Захаваць",
"nextTotpTitle": "наступны",
"nextTotpTitle": "далей",
"deleteCodeTitle": "Выдаліць код?",
"deleteCodeMessage": "Вы сапраўды хочаце выдаліць гэты код? Гэта дзеянне з'яўляецца незваротным.",
"trashCode": "Выдаліць код?",
"trashCodeMessage": "Вы сапраўды хочаце выдаліць код для {account}?",
"trash": "Сметніца",
"viewLogsAction": "Паглядзець журнал",
"preparingLogsTitle": "Падрыхтоўка журнала...",
"viewLogsAction": "Паглядзець журналы",
"preparingLogsTitle": "Падрыхтоўка журналаў...",
"emailLogsTitle": "Адправіць журнал па электроннай пошце",
"exportLogsAction": "Экспартаваць журнал",
"reportABug": "Паведаміць пра памылку",
"reportBug": "Паведаміць пра памылку",
"emailLogsMessage": "Адпраўце журналы на {email}",
"@emailLogsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"copyEmailAction": "Скапіяваць электронную пошту",
"exportLogsAction": "Экспартаваць журналы",
"reportABug": "Паведаміць аб памылцы",
"crashAndErrorReporting": "Справаздачы аб збоях і памылках",
"reportBug": "Паведаміць аб памылцы",
"emailUsMessage": "Адпраўце нам ліст на {email}",
"@emailUsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"contactSupport": "Звярнуцца ў службу падтрымкі",
"rateUsOnStore": "Ацаніць нас у {storeName}",
"blog": "Блог",
"verifyPassword": "Праверыць пароль",
"pleaseWait": "Пачакайце...",
@@ -66,32 +85,128 @@
"useRecoveryKey": "Выкарыстоўваць ключ аднаўлення",
"incorrectPasswordTitle": "Няправільны пароль",
"welcomeBack": "З вяртаннем!",
"changeEmail": "Змяніць адрас электроннай пошты",
"changePassword": "Змяніць пароль",
"data": "Даныя",
"importCodes": "Імпартаваць коды",
"importTypePlainText": "Звычайны тэкст",
"importTypeEnteEncrypted": "Шыфраванне экспартавання з Ente",
"passwordForDecryptingExport": "Пароль для дэшыфроўкі экспартавання",
"passwordEmptyError": "Пароль не можа быць пустым",
"importFromApp": "Імпартаваць коды з {appName}",
"exportCodes": "Экспартаваць коды",
"importLabel": "Імпарт",
"selectFile": "Выбраць файл",
"ok": "OK",
"cancel": "Скасаваць",
"yes": "Так",
"no": "Не",
"email": "Электронная пошта",
"support": "Падтрымка",
"general": "Агульныя",
"settings": "Налады",
"copied": "Скапіявана",
"pleaseTryAgain": "Калі ласка, паспрабуйце яшчэ раз",
"delete": "Выдаліць",
"enterYourPasswordHint": "Увядзіце ваш пароль",
"forgotPassword": "Забылі пароль",
"oops": "Вой",
"faq": "Частыя пытанні",
"scan": "Сканіраваць",
"verify": "Праверыць",
"verifyPasskey": "Праверыць ключ доступу",
"loginWithTOTP": "Увайсці з TOTP",
"recoverAccount": "Аднавіць уліковы запіс",
"recover": "Аднавіць",
"invalidQRCode": "Памылковы QR-код",
"deleteAccount": "Выдаліць уліковы запіс",
"noDeleteAccountAction": "Не, выдаліць уліковы запіс",
"sendEmail": "Адправіць ліст",
"createNewAccount": "Стварыць новы ўліковы запіс",
"weakStrength": "Ненадзейны",
"strongStrength": "Надзейны",
"moderateStrength": "Умераная",
"confirmPassword": "Пацвердзіць пароль",
"close": "Закрыць",
"oopsSomethingWentWrong": "Штосьці пайшло не так.",
"selectLanguage": "Выберыце мову",
"language": "Мова",
"social": "Сацыяльныя сеткі",
"security": "Бяспека",
"searchHint": "Пошук...",
"search": "Пошук"
"search": "Пошук",
"addCode": "Дадаць код",
"scanAQrCode": "Сканіраваць QR-код",
"edit": "Рэдагаваць",
"share": "Абагуліць",
"restore": "Аднавіць",
"error": "Памылка",
"saveKey": "Захаваць ключ",
"save": "Захаваць",
"send": "Адправіць",
"back": "Назад",
"createAccount": "Стварыць уліковы запіс",
"password": "Пароль",
"privacyPolicyTitle": "Палітыка прыватнасці",
"termsOfServicesTitle": "Умовы",
"changePasswordTitle": "Змяніць пароль",
"resetPasswordTitle": "Скінуць пароль",
"encryptionKeys": "Ключы шыфравання",
"continueLabel": "Працягнуць",
"insecureDevice": "Небяспечная прылада",
"logInLabel": "Увайсці",
"logout": "Выйсці",
"exit": "Выхад",
"theme": "Тема",
"lightTheme": "Светлая",
"darkTheme": "Цёмная",
"systemTheme": "Сістэманая",
"confirm": "Пацвердзіць",
"emailYourLogs": "Адправіць журналы",
"about": "Аб праграме",
"privacy": "Прыватнасць",
"terms": "Умовы",
"downloadUpdate": "Спампаваць",
"update": "Абнавіць",
"warning": "Папярэджанне",
"importSuccessTitle": "Ура!",
"sorry": "Прабачце",
"pendingSyncs": "Папярэджанне",
"manualSort": "Карыстальніцкая",
"incorrectCode": "Няправільны код",
"enterPassword": "Увядзіце пароль",
"export": "Экспартаваць",
"singIn": "Увайсці",
"androidBiometricSuccess": "Паспяхова",
"@androidBiometricSuccess": {
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
},
"androidCancelButton": "Скасаваць",
"@androidCancelButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
},
"goToSettings": "Перайсці ў налады",
"@goToSettings": {
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
},
"passkey": "Ключ доступу",
"pinText": "Замацаваць",
"unpinText": "Адмацаваць",
"pinned": "Замацавана",
"tags": "Тэгі",
"createNewTag": "Стварыць новы тэг",
"tag": "Тэг",
"create": "Стварыць",
"viewRawCodes": "Паглядзець неапрацаваныя коды",
"rawCodeData": "Неапрацаваныя даныя кода",
"appLock": "Блакіроўка праграмы",
"next": "Далей",
"tapToUnlock": "Націсніце для разблакіроўкі",
"type": "Тып",
"period": "Перыяд",
"digits": "Лічбы"
}

View File

@@ -173,6 +173,7 @@
"invalidQRCode": "Ogiltig QR-kod",
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
"enterEmailHint": "Ange din e-postadress",
"enterNewEmailHint": "Ange din nya e-postadress",
"invalidEmailTitle": "Ogiltig e-postadress",
"invalidEmailMessage": "Ange en giltig e-postadress.",
"deleteAccount": "Radera konto",
@@ -368,16 +369,19 @@
"signInToBackup": "Logga in för att säkerhetskopiera dina koder",
"singIn": "Logga in",
"sigInBackupReminder": "Vänligen exportera dina koder för att säkerställa att du har en säkerhetskopia som du kan återställa från.",
"offlineModeWarning": "Du har valt att fortsätta utan säkerhetskopior. Vänligen ta manuella säkerhetskopior för att se till att dina koder är säkra.",
"showLargeIcons": "Visa stora ikoner",
"compactMode": "Kompakt läge",
"shouldHideCode": "Dölj koder",
"doubleTapToViewHiddenCode": "Du kan dubbeltrycka på en post för att visa koden",
"focusOnSearchBar": "Fokusera på sök vid appstart",
"confirmUpdatingkey": "Är du säker på att du vill uppdatera den hemliga nyckeln?",
"minimizeAppOnCopy": "Minimera appen vid kopiering",
"editCodeAuthMessage": "Autentisera för att redigera kod",
"deleteCodeAuthMessage": "Autentisera för att radera kod",
"showQRAuthMessage": "Autentisera för att visa QR-kod",
"confirmAccountDeleteTitle": "Bekräfta radering av kontot",
"confirmAccountDeleteMessage": "Detta konto är kopplat till andra Ente apps, om du använder någon.\n\nDina uppladdade data, över alla Ente appar, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.",
"androidBiometricHint": "Verifiera identitet",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@@ -414,6 +418,18 @@
"@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": "Biometrisk autentisering är inte konfigurerad på din enhet. Gå till \"Inställningar > Säkerhet\" för att lägga till biometrisk autentisering.",
"@androidGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
},
"iOSLockOut": "Biometrisk autentisering är inaktiverat. Lås och lås upp din skärm för att aktivera den.",
"@iOSLockOut": {
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
},
"iOSGoToSettingsDescription": "Biometrisk autentisering är inte konfigurerad på din enhet. Aktivera antingen Touch ID eller Face ID på din telefon.",
"@iOSGoToSettingsDescription": {
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
},
"iOSOkButton": "OK",
"@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
@@ -421,6 +437,7 @@
"noInternetConnection": "Ingen internetanslutning",
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
"signOutFromOtherDevices": "Logga ut från andra enheter",
"signOutOtherBody": "Om du tror att någon kanske känner till ditt lösenord kan du tvinga alla andra enheter med ditt konto att logga ut.",
"signOutOtherDevices": "Logga ut andra enheter",
"doNotSignOut": "Logga inte ut",
"hearUsWhereTitle": "Hur hörde du talas om Ente? (valfritt)",
@@ -450,6 +467,7 @@
"create": "Skapa",
"editTag": "Redigera tagg",
"deleteTagTitle": "Radera tagg?",
"deleteTagMessage": "Vill du ta bort den här koden? Det går inte att ångra den här åtgärden.",
"somethingWentWrongParsingCode": "Vi kunde inte tolka {x} koder.",
"updateNotAvailable": "Uppdateringen är inte tillgänglig",
"viewRawCodes": "Visa råa koder",

View File

@@ -173,6 +173,7 @@
"invalidQRCode": "QR 碼無效",
"noRecoveryKeyTitle": "沒有復原密鑰嗎?",
"enterEmailHint": "請輸入您的電子郵件地址",
"enterNewEmailHint": "輸入你的新電子郵件地址",
"invalidEmailTitle": "無效的電子郵件地址",
"invalidEmailMessage": "請輸入一個有效的電子郵件地址。",
"deleteAccount": "刪除帳戶",

View File

@@ -39,7 +39,7 @@
"next-electron-server": "^1.0.0",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.20.1",
"zod": "^3.25.48"
"zod": "^3.25.51"
},
"devDependencies": {
"@eslint/js": "^9.28.0",
@@ -49,7 +49,7 @@
"ajv": "^8.17.1",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"electron": "^36.3.2",
"electron": "^36.4.0",
"electron-builder": "^26.0.14",
"eslint": "^9",
"prettier": "3.5.3",
@@ -57,7 +57,7 @@
"prettier-plugin-packagejson": "^2.5.15",
"shx": "^0.4.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.33.0"
"typescript-eslint": "^8.33.1"
},
"packageManager": "yarn@1.22.22",
"productName": "ente"

View File

@@ -50,8 +50,8 @@ import { convertToJPEG, generateImageThumbnail } from "./services/image";
import { logout } from "./services/logout";
import {
lastShownChangelogVersion,
masterKeyB64,
saveMasterKeyB64,
masterKeyFromSafeStorage,
saveMasterKeyInSafeStorage,
setLastShownChangelogVersion,
} from "./services/store";
import {
@@ -108,10 +108,12 @@ export const attachIPCHandlers = () => {
ipcMain.handle("selectDirectory", () => selectDirectory());
ipcMain.handle("masterKeyB64", () => masterKeyB64());
ipcMain.handle("masterKeyFromSafeStorage", () =>
masterKeyFromSafeStorage(),
);
ipcMain.handle("saveMasterKeyB64", (_, masterKeyB64: string) =>
saveMasterKeyB64(masterKeyB64),
ipcMain.handle("saveMasterKeyInSafeStorage", (_, masterKey: string) =>
saveMasterKeyInSafeStorage(masterKey),
);
ipcMain.handle("lastShownChangelogVersion", () =>

View File

@@ -12,7 +12,7 @@ import fs_ from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { Readable } from "node:stream";
import { z } from "zod";
import { z } from "zod/v4";
import type { FFmpegCommand } from "../../types/ipc";
import log from "../log-worker";
import { messagePortMainEndpoint } from "../utils/comlink";

View File

@@ -15,7 +15,7 @@ import { existsSync } from "fs";
import fs from "node:fs/promises";
import path from "node:path";
import * as ort from "onnxruntime-node";
import { z } from "zod";
import { z } from "zod/v4";
import log from "../log-worker";
import { messagePortMainEndpoint } from "../utils/comlink";
import { wait } from "../utils/common";

View File

@@ -24,17 +24,17 @@ export const clearStores = () => {
* On macOS, `safeStorage` stores our data under a Keychain entry named
* "<app-name> Safe Storage". In our case, "ente Safe Storage".
*/
export const saveMasterKeyB64 = (masterKeyB64: string) => {
const encryptedKey = safeStorage.encryptString(masterKeyB64);
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
safeStorageStore.set("encryptionKey", b64EncryptedKey);
export const saveMasterKeyInSafeStorage = (masterKey: string) => {
const encryptedKeyBuffer = safeStorage.encryptString(masterKey);
const encryptedKey = Buffer.from(encryptedKeyBuffer).toString("base64");
safeStorageStore.set("encryptionKey", encryptedKey);
};
export const masterKeyB64 = (): string | undefined => {
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
if (!b64EncryptedKey) return undefined;
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return safeStorage.decryptString(keyBuffer);
export const masterKeyFromSafeStorage = (): string | undefined => {
const encryptedKey = safeStorageStore.get("encryptionKey");
if (!encryptedKey) return undefined;
const encryptedKeyBuffer = Buffer.from(encryptedKey, "base64");
return safeStorage.decryptString(encryptedKeyBuffer);
};
export const lastShownChangelogVersion = (): number | undefined =>

View File

@@ -28,6 +28,13 @@ export const createWatcher = (mainWindow: BrowserWindow) => {
// Ask the watcher to wait for a the file size to stabilize before
// telling us about a new file. By default, it waits for 2 seconds.
awaitWriteFinish: true,
// On macOS we start getting "EMFILE: too many open files" when watching
// large folders. This is a known regression in Chokidar v4:
// https://github.com/paulmillr/chokidar/issues/1385
//
// The recommended workaround for now is to enable usePolling. Since it
// comes at a performance cost, we only do it where needed (macOS).
...(process.platform == "darwin" ? { usePolling: true } : {}),
});
watcher

View File

@@ -113,10 +113,11 @@ const logout = () => {
return ipcRenderer.invoke("logout");
};
const masterKeyB64 = () => ipcRenderer.invoke("masterKeyB64");
const masterKeyFromSafeStorage = () =>
ipcRenderer.invoke("masterKeyFromSafeStorage");
const saveMasterKeyB64 = (masterKeyB64: string) =>
ipcRenderer.invoke("saveMasterKeyB64", masterKeyB64);
const saveMasterKeyInSafeStorage = (masterKey: string) =>
ipcRenderer.invoke("saveMasterKeyInSafeStorage", masterKey);
const lastShownChangelogVersion = () =>
ipcRenderer.invoke("lastShownChangelogVersion");
@@ -358,8 +359,8 @@ contextBridge.exposeInMainWorld("electron", {
selectDirectory,
pathForFile,
logout,
masterKeyB64,
saveMasterKeyB64,
masterKeyFromSafeStorage,
saveMasterKeyInSafeStorage,
lastShownChangelogVersion,
setLastShownChangelogVersion,
isAutoLaunchEnabled,

View File

@@ -392,78 +392,78 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.0.tgz#51ed03649575ba51bcee7efdbfd85283249b5447"
integrity sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==
"@typescript-eslint/eslint-plugin@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz#532641b416ed2afd5be893cddb2a58e9cd1f7a3e"
integrity sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==
dependencies:
"@eslint-community/regexpp" "^4.10.0"
"@typescript-eslint/scope-manager" "8.33.0"
"@typescript-eslint/type-utils" "8.33.0"
"@typescript-eslint/utils" "8.33.0"
"@typescript-eslint/visitor-keys" "8.33.0"
"@typescript-eslint/scope-manager" "8.33.1"
"@typescript-eslint/type-utils" "8.33.1"
"@typescript-eslint/utils" "8.33.1"
"@typescript-eslint/visitor-keys" "8.33.1"
graphemer "^1.4.0"
ignore "^7.0.0"
natural-compare "^1.4.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/parser@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.0.tgz#8e523c2b447ad7cd6ac91b719d8b37449481784d"
integrity sha512-JaehZvf6m0yqYp34+RVnihBAChkqeH+tqqhS0GuX1qgPpwLvmTPheKEs6OeCK6hVJgXZHJ2vbjnC9j119auStQ==
"@typescript-eslint/parser@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.1.tgz#ef9a5ee6aa37a6b4f46cc36d08a14f828238afe2"
integrity sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==
dependencies:
"@typescript-eslint/scope-manager" "8.33.0"
"@typescript-eslint/types" "8.33.0"
"@typescript-eslint/typescript-estree" "8.33.0"
"@typescript-eslint/visitor-keys" "8.33.0"
"@typescript-eslint/scope-manager" "8.33.1"
"@typescript-eslint/types" "8.33.1"
"@typescript-eslint/typescript-estree" "8.33.1"
"@typescript-eslint/visitor-keys" "8.33.1"
debug "^4.3.4"
"@typescript-eslint/project-service@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.0.tgz#71f37ef9010de47bf20963914743c5cbef851e08"
integrity sha512-d1hz0u9l6N+u/gcrk6s6gYdl7/+pp8yHheRTqP6X5hVDKALEaTn8WfGiit7G511yueBEL3OpOEpD+3/MBdoN+A==
"@typescript-eslint/project-service@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.1.tgz#c85e7d9a44d6a11fe64e73ac1ed47de55dc2bf9f"
integrity sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.33.0"
"@typescript-eslint/types" "^8.33.0"
"@typescript-eslint/tsconfig-utils" "^8.33.1"
"@typescript-eslint/types" "^8.33.1"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.0.tgz#459cf0c49d410800b1a023b973c62d699b09bf4c"
integrity sha512-LMi/oqrzpqxyO72ltP+dBSP6V0xiUb4saY7WLtxSfiNEBI8m321LLVFU9/QDJxjDQG9/tjSqKz/E3380TEqSTw==
"@typescript-eslint/scope-manager@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz#d1e0efb296da5097d054bc9972e69878a2afea73"
integrity sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==
dependencies:
"@typescript-eslint/types" "8.33.0"
"@typescript-eslint/visitor-keys" "8.33.0"
"@typescript-eslint/types" "8.33.1"
"@typescript-eslint/visitor-keys" "8.33.1"
"@typescript-eslint/tsconfig-utils@8.33.0", "@typescript-eslint/tsconfig-utils@^8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.0.tgz#316adab038bbdc43e448781d5a816c2973eab73e"
integrity sha512-sTkETlbqhEoiFmGr1gsdq5HyVbSOF0145SYDJ/EQmXHtKViCaGvnyLqWFFHtEXoS0J1yU8Wyou2UGmgW88fEug==
"@typescript-eslint/tsconfig-utils@8.33.1", "@typescript-eslint/tsconfig-utils@^8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz#7836afcc097a4657a5ed56670851a450d8b70ab8"
integrity sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==
"@typescript-eslint/type-utils@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.0.tgz#f06124b2d6db8a51b24990cb123c9543af93fef5"
integrity sha512-lScnHNCBqL1QayuSrWeqAL5GmqNdVUQAAMTaCwdYEdWfIrSrOGzyLGRCHXcCixa5NK6i5l0AfSO2oBSjCjf4XQ==
"@typescript-eslint/type-utils@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz#d73ee1a29d8a0abe60d4abbff4f1d040f0de15fa"
integrity sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==
dependencies:
"@typescript-eslint/typescript-estree" "8.33.0"
"@typescript-eslint/utils" "8.33.0"
"@typescript-eslint/typescript-estree" "8.33.1"
"@typescript-eslint/utils" "8.33.1"
debug "^4.3.4"
ts-api-utils "^2.1.0"
"@typescript-eslint/types@8.33.0", "@typescript-eslint/types@^8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.0.tgz#02a7dbba611a8abf1ad2a9e00f72f7b94b5ab0ee"
integrity sha512-DKuXOKpM5IDT1FA2g9x9x1Ug81YuKrzf4mYX8FAVSNu5Wo/LELHWQyM1pQaDkI42bX15PWl0vNPt1uGiIFUOpg==
"@typescript-eslint/types@8.33.1", "@typescript-eslint/types@^8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.1.tgz#b693111bc2180f8098b68e9958cf63761657a55f"
integrity sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==
"@typescript-eslint/typescript-estree@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.0.tgz#abcc1d3db75a8e9fd2e274ee8c4099fa2399abfd"
integrity sha512-vegY4FQoB6jL97Tu/lWRsAiUUp8qJTqzAmENH2k59SJhw0Th1oszb9Idq/FyyONLuNqT1OADJPXfyUNOR8SzAQ==
"@typescript-eslint/typescript-estree@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz#d271beed470bc915b8764e22365d4925c2ea265d"
integrity sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==
dependencies:
"@typescript-eslint/project-service" "8.33.0"
"@typescript-eslint/tsconfig-utils" "8.33.0"
"@typescript-eslint/types" "8.33.0"
"@typescript-eslint/visitor-keys" "8.33.0"
"@typescript-eslint/project-service" "8.33.1"
"@typescript-eslint/tsconfig-utils" "8.33.1"
"@typescript-eslint/types" "8.33.1"
"@typescript-eslint/visitor-keys" "8.33.1"
debug "^4.3.4"
fast-glob "^3.3.2"
is-glob "^4.0.3"
@@ -471,22 +471,22 @@
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.0.tgz#574ad5edee371077b9e28ca6fb804f2440f447c1"
integrity sha512-lPFuQaLA9aSNa7D5u2EpRiqdAUhzShwGg/nhpBlc4GR6kcTABttCuyjFs8BcEZ8VWrjCBof/bePhP3Q3fS+Yrw==
"@typescript-eslint/utils@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.1.tgz#ea22f40d3553da090f928cf17907e963643d4b96"
integrity sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==
dependencies:
"@eslint-community/eslint-utils" "^4.7.0"
"@typescript-eslint/scope-manager" "8.33.0"
"@typescript-eslint/types" "8.33.0"
"@typescript-eslint/typescript-estree" "8.33.0"
"@typescript-eslint/scope-manager" "8.33.1"
"@typescript-eslint/types" "8.33.1"
"@typescript-eslint/typescript-estree" "8.33.1"
"@typescript-eslint/visitor-keys@8.33.0":
version "8.33.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.0.tgz#fbae16fd3594531f8cad95d421125d634e9974fe"
integrity sha512-7RW7CMYoskiz5OOGAWjJFxgb7c5UNjTG292gYhWeOAcFmYCtVCSqjqSBj5zMhxbXo2JOW95YYrUWJfU0zrpaGQ==
"@typescript-eslint/visitor-keys@8.33.1":
version "8.33.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz#6c6e002c24d13211df3df851767f24dfdb4f42bc"
integrity sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==
dependencies:
"@typescript-eslint/types" "8.33.0"
"@typescript-eslint/types" "8.33.1"
eslint-visitor-keys "^4.2.0"
"@xmldom/xmldom@^0.8.8":
@@ -1250,10 +1250,10 @@ electron-updater@^6.6.3:
semver "^7.6.3"
tiny-typed-emitter "^2.1.0"
electron@^36.3.2:
version "36.3.2"
resolved "https://registry.yarnpkg.com/electron/-/electron-36.3.2.tgz#4a60f95e8d3858d01570c03b58dc2fb2f17ee8b6"
integrity sha512-v0/j7n22CL3OYv9BIhq6JJz2+e1HmY9H4bjTk8/WzVT9JwVX/T/21YNdR7xuQ6XDSEo9gP5JnqmjOamE+CUY8Q==
electron@^36.4.0:
version "36.4.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-36.4.0.tgz#9463bf5fa7565ae7be3a274f7f6a46359bcfe74d"
integrity sha512-LLOOZEuW5oqvnjC7HBQhIqjIIJAZCIFjQxltQGLfEC7XFsBoZgQ3u3iFj+Kzw68Xj97u1n57Jdt7P98qLvUibQ==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^22.7.7"
@@ -3251,14 +3251,14 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
typescript-eslint@^8.33.0:
version "8.33.0"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.33.0.tgz#89f733a90edc6abe0994b6130b964e781a1ba82f"
integrity sha512-5YmNhF24ylCsvdNW2oJwMzTbaeO4bg90KeGtMjUw0AGtHksgEPLRTUil+coHwCfiu4QjVJFnjp94DmU6zV7DhQ==
typescript-eslint@^8.33.1:
version "8.33.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.33.1.tgz#d2d59c9b24afe1f903a855b02145802e4ae930ff"
integrity sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==
dependencies:
"@typescript-eslint/eslint-plugin" "8.33.0"
"@typescript-eslint/parser" "8.33.0"
"@typescript-eslint/utils" "8.33.0"
"@typescript-eslint/eslint-plugin" "8.33.1"
"@typescript-eslint/parser" "8.33.1"
"@typescript-eslint/utils" "8.33.1"
typescript@^5.4.3, typescript@^5.8.3:
version "5.8.3"
@@ -3421,7 +3421,7 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zod@^3.25.48:
version "3.25.48"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.48.tgz#6c2b536fbb519905e8f4a4ac58743de4d5331bb2"
integrity sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q==
zod@^3.25.51:
version "3.25.51"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.51.tgz#aa2cf648e54f6f060f139cf77b694819f63c9f3a"
integrity sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==

View File

@@ -6,15 +6,16 @@ description: >
# Community
We are building Ente in the open with our community on
[GitHub](https://github.com/ente-io/ente) and [Discord](https://ente.io/discord)
## Blog
To stay up to date with new product launches, and behind the scenes details of
how we're building Ente, you can read our [blog](https://ente.io/blog) (or
subscribe to it via [RSS](https://ente.io/blog/rss.xml))
## Community
## Socials
Or if you'd just like to hang out, join our
[Discord](https://discord.gg/z2YVKkycX3), follow us on
[Twitter](https://twitter.com/enteio) or give us a shout out on
You can also follow us on [Twitter](https://twitter.com/enteio) or toot to us on
[Mastodon](https://mstdn.social/@ente)

View File

@@ -5,7 +5,13 @@ description: Details about how to contribute to Ente
# Contributing
## Suggest a feature
There are many ways to contribute to Ente. By spreading the word, engaging with
our community, helping us with translations or documentation.
You can find our contribution guidelines
[here](https://github.com/ente-io/ente/blob/main/CONTRIBUTING.md).
## Suggesting features
To suggest new features and/or offer your perspective on how we should design
(planned and upcoming features), use our

View File

@@ -6,8 +6,8 @@ description: Get help from Ente via customer support and community
# Help
If you encounter any issues with any of the products that's not answered by our
[documentation](/), please reach out to our Customer Support by sending an email
to [support@ente.io](mailto:support@ente.io)
[docs](/), please reach out to our team by sending an email to
[support@ente.io](mailto:support@ente.io)
For community support, please post your queries on
[Discord](https://discord.gg/z2YVKkycX3)

View File

@@ -103,43 +103,19 @@ clicking on "Your map" under "Locations" on the search screen.
On the login page, enter your email and click on Forgot Password. Then, enter
your recovery key and create a new password.
# iOS Album Backup and Organization in Ente
### How does Ente handle photos that are part of multiple iOS albums?
When you select multiple albums for backup, Ente prioritizes uploading each
photo to the album with the fewest photos. This means a photo will only be
uploaded once, even if it exists in multiple albums on your device. If you
create new albums on your device after the initial backup, those photos may not
appear in the corresponding Ente album if they were already uploaded to a
different album.
### Why dont all photos from a new iOS album appear in the corresponding Ente album?
If you create a new album on your device after the initial backup, the photos in
that album may have already been uploaded to another album in Ente. To fix this,
go to the "On Device" album in Ente, select all photos, and manually add them to
the corresponding album in Ente.
### What happens if I reorganize my photos in the iOS Photos app after backing up?
Reorganizing photos in the iOS Photos app (e.g., moving photos to new albums)
wont automatically reflect in Ente. Youll need to manually add those photos to
the corresponding albums in Ente to maintain consistency.
### Can I search for photos using the descriptions Ive added?
## Can I search for photos using the descriptions Ive added?
Yes, descriptions are searchable, making it easier to find specific photos
later. To do this, open the photo, tap the (i) button, and enter your
description.
### How does the deduplication feature work on the desktop app?
## How does the deduplication feature work on the desktop app?
If the app finds exact duplicates, it will show them in the deduplication. When
you delete a duplicate, the app keeps one copy and creates a symlink for the
other duplicate. This helps save storage space.
### What happens if I lose access to my email address? Can I use my recovery key to bypass email verification?
## What happens if I lose access to my email address? Can I use my recovery key to bypass email verification?
No, the recovery key does not bypass email verification. For security reasons,
we do not disable or bypass email verification unless the account owner reaches
@@ -148,3 +124,29 @@ their account.
If you lose access to your email, please contact our support team at
support@ente.io
---
# iOS Album Backup and Organization in Ente
## How does Ente handle photos that are part of multiple iOS albums?
When you select multiple albums for backup, Ente prioritizes uploading each
photo to the album with the fewest photos. This means a photo will only be
uploaded once, even if it exists in multiple albums on your device. If you
create new albums on your device after the initial backup, those photos may not
appear in the corresponding Ente album if they were already uploaded to a
different album.
## Why dont all photos from a new iOS album appear in the corresponding Ente album?
If you create a new album on your device after the initial backup, the photos in
that album may have already been uploaded to another album in Ente. To fix this,
go to the "On Device" album in Ente, select all photos, and manually add them to
the corresponding album in Ente.
## What happens if I reorganize my photos in the iOS Photos app after backing up?
Reorganizing photos in the iOS Photos app (e.g., moving photos to new albums)
wont automatically reflect in Ente. Youll need to manually add those photos to
the corresponding albums in Ente to maintain consistency.

View File

@@ -52,6 +52,11 @@ Ente also provides a tool for manual de-duplication in _Settings → Backup →
Remove duplicates_. This is useful if you have an existing library with
duplicates across different albums, but wish to keep only one copy.
During this operation, Ente will discard duplicates across all albums, retain a
single copy, and add symlinks to this copy within all existing albums. So your
existing album structure remains unchanged, while the space consumed by the
duplicate data is freed up.
## Adding to Ente album creates symlinks
Note that once a file is in Ente, adding it to another Ente album will create a

View File

@@ -53,7 +53,7 @@ analyzer:
sort_child_properties_last: warning
sort_pub_dependencies: warning
library_private_types_in_public_api: warning
constant_identifier_names: warning
constant_identifier_names: ignore
prefer_const_constructors: warning
prefer_const_declarations: warning
prefer_const_constructors_in_immutables: warning

View File

@@ -153,6 +153,20 @@
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/memory_widget" />
</receiver>
<receiver android:name="EnteAlbumsWidgetProvider" android:label="Albums" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/albums_widget" />
</receiver>
<receiver android:name="EntePeopleWidgetProvider" android:label="People" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="@xml/people_widget" />
</receiver>
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationReceiver" /> <!-- Needed for scheduling notifications -->
<receiver android:exported="false" android:name="com.dexterous.flutterlocalnotifications.ScheduledNotificationBootReceiver"> <!-- Needed for scheduling notifications -->
<intent-filter>

View File

@@ -0,0 +1,195 @@
package io.ente.photos
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class AlbumsFileData(
val title: String?,
val subText: String?,
val generatedId: Int?,
val mainKey: String?
)
class EnteAlbumsWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
widgetData: SharedPreferences
) {
appWidgetIds.forEach { widgetId ->
val views =
RemoteViews(context.packageName, R.layout.albums_widget_layout)
.apply {
val totalAlbums =
widgetData.getInt("totalAlbums", 0)
var randomNumber = -1
var imagePath: String? = null
if (totalAlbums > 0) {
randomNumber =
(0 until totalAlbums!!).random()
imagePath =
widgetData.getString(
"albums_widget_" +
randomNumber,
null
)
}
var imageExists: Boolean = false
if (imagePath != null) {
val imageFile = File(imagePath)
imageExists = imageFile.exists()
}
if (imageExists) {
val data =
widgetData.getString(
"albums_widget_${randomNumber}_data",
null
)
val decoded: AlbumsFileData? =
data?.let {
Json.decodeFromString<
AlbumsFileData>(it)
}
val title = decoded?.title
val subText = decoded?.subText
val generatedId = decoded?.generatedId
val mainKey = decoded?.mainKey
val deepLinkUri =
Uri.parse(
"albumwidget://message?generatedId=${generatedId}&mainKey=${mainKey}&homeWidget"
)
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
deepLinkUri
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EnteAlbumsWidgetProvider",
"Image exists: $imagePath"
)
setViewVisibility(
R.id.widget_img,
View.VISIBLE
)
setViewVisibility(
R.id.widget_subtitle,
View.VISIBLE
)
setViewVisibility(
R.id.widget_title,
View.VISIBLE
)
setViewVisibility(
R.id.widget_overlay,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.GONE
)
val bitmap: Bitmap =
BitmapFactory.decodeFile(imagePath)
setImageViewBitmap(R.id.widget_img, bitmap)
setTextViewText(R.id.widget_title, title)
setTextViewText(
R.id.widget_subtitle,
subText
)
} else {
// Open App on Widget Click
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EnteAlbumsWidgetProvider",
"Image doesn't exists"
)
setViewVisibility(
R.id.widget_img,
View.GONE
)
setViewVisibility(
R.id.widget_subtitle,
View.GONE
)
setViewVisibility(
R.id.widget_title,
View.GONE
)
setViewVisibility(
R.id.widget_overlay,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.VISIBLE
)
val drawable =
ContextCompat.getDrawable(
context,
R.drawable.ic_albums_widget
)
val bitmap =
(drawable as BitmapDrawable).bitmap
setImageViewBitmap(
R.id.widget_placeholder,
bitmap
)
}
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}

View File

@@ -91,10 +91,6 @@ class EnteMemoryWidgetProvider : HomeWidgetProvider() {
R.id.widget_img,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.VISIBLE
)
setViewVisibility(
R.id.widget_subtitle,
View.VISIBLE
@@ -148,10 +144,6 @@ class EnteMemoryWidgetProvider : HomeWidgetProvider() {
R.id.widget_img,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.GONE
)
setViewVisibility(
R.id.widget_subtitle,
View.GONE
@@ -181,7 +173,7 @@ class EnteMemoryWidgetProvider : HomeWidgetProvider() {
ContextCompat.getDrawable(
context,
R.drawable
.ic_home_widget_default
.ic_memories_widget
)
val bitmap =
(drawable as BitmapDrawable).bitmap

View File

@@ -0,0 +1,195 @@
package io.ente.photos
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.util.Log
import android.view.View
import android.widget.RemoteViews
import androidx.core.content.ContextCompat
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
import java.io.File
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class PeopleFileData(
val title: String?,
val subText: String?,
val generatedId: Int?,
val mainKey: String?
)
class EntePeopleWidgetProvider : HomeWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
widgetData: SharedPreferences
) {
appWidgetIds.forEach { widgetId ->
val views =
RemoteViews(context.packageName, R.layout.people_widget_layout)
.apply {
val totalPeople =
widgetData.getInt("totalPeople", 0)
var randomNumber = -1
var imagePath: String? = null
if (totalPeople > 0) {
randomNumber =
(0 until totalPeople!!).random()
imagePath =
widgetData.getString(
"people_widget_" +
randomNumber,
null
)
}
var imageExists: Boolean = false
if (imagePath != null) {
val imageFile = File(imagePath)
imageExists = imageFile.exists()
}
if (imageExists) {
val data =
widgetData.getString(
"people_widget_${randomNumber}_data",
null
)
val decoded: PeopleFileData? =
data?.let {
Json.decodeFromString<
PeopleFileData>(it)
}
val title = decoded?.title
val subText = decoded?.subText
val generatedId = decoded?.generatedId
val mainKey = decoded?.mainKey
val deepLinkUri =
Uri.parse(
"peoplewidget://message?generatedId=${generatedId}&mainKey=${mainKey}&homeWidget"
)
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
deepLinkUri
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EntePeopleWidgetProvider",
"Image exists: $imagePath"
)
setViewVisibility(
R.id.widget_img,
View.VISIBLE
)
setViewVisibility(
R.id.widget_subtitle,
View.VISIBLE
)
setViewVisibility(
R.id.widget_title,
View.VISIBLE
)
setViewVisibility(
R.id.widget_overlay,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.GONE
)
val bitmap: Bitmap =
BitmapFactory.decodeFile(imagePath)
setImageViewBitmap(R.id.widget_img, bitmap)
setTextViewText(R.id.widget_title, title)
setTextViewText(
R.id.widget_subtitle,
subText
)
} else {
// Open App on Widget Click
val pendingIntent =
HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java
)
setOnClickPendingIntent(
R.id.widget_container,
pendingIntent
)
Log.d(
"EntePeopleWidgetProvider",
"Image doesn't exists"
)
setViewVisibility(
R.id.widget_img,
View.GONE
)
setViewVisibility(
R.id.widget_subtitle,
View.GONE
)
setViewVisibility(
R.id.widget_title,
View.GONE
)
setViewVisibility(
R.id.widget_overlay,
View.GONE
)
setViewVisibility(
R.id.widget_placeholder,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_text,
View.VISIBLE
)
setViewVisibility(
R.id.widget_placeholder_container,
View.VISIBLE
)
val drawable =
ContextCompat.getDrawable(
context,
R.drawable.ic_people_widget
)
val bitmap =
(drawable as BitmapDrawable).bitmap
setImageViewBitmap(
R.id.widget_placeholder,
bitmap
)
}
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/widget_container">
<!-- Main Image (if available) -->
<ImageView
android:id="@+id/widget_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone" /> <!-- Initially hidden -->
<!-- Gradient Overlay for Text Readability -->
<LinearLayout
android:id="@+id/widget_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:paddingTop="4dp"
android:background="@layout/gradient_overlay"
android:visibility="gone"> <!-- Initially hidden, shown when image is available -->
<!-- Title -->
<TextView
android:id="@+id/widget_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone"/>
<!-- Subtitle -->
<TextView
android:id="@+id/widget_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone"/>
</LinearLayout>
<!-- Placeholder View (when no image available) -->
<LinearLayout
android:id="@+id/widget_placeholder_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="@color/widget_placeholder_bg">
<ImageView
android:id="@+id/widget_placeholder"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_albums_widget"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/widget_placeholder_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Go to Settings -> General to customise the widget"
android:textSize="12sp"
android:gravity="center_horizontal"
android:textColor="@color/widget_text_color"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:layout_marginTop="12dp"/>
</LinearLayout>
</FrameLayout>

View File

@@ -65,15 +65,15 @@
android:id="@+id/widget_placeholder"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_home_widget_default"
android:src="@drawable/ic_memories_widget"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/widget_placeholder_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Your memories will appear here"
android:textSize="14sp"
android:text="Go to Settings -> General to customise the widget"
android:textSize="12sp"
android:gravity="center_horizontal"
android:textColor="@color/widget_text_color"
android:paddingStart="8dp"

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/widget_container">
<!-- Main Image (if available) -->
<ImageView
android:id="@+id/widget_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:visibility="gone" /> <!-- Initially hidden -->
<!-- Gradient Overlay for Text Readability -->
<LinearLayout
android:id="@+id/widget_overlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingBottom="16dp"
android:paddingTop="4dp"
android:background="@layout/gradient_overlay"
android:visibility="gone"> <!-- Initially hidden, shown when image is available -->
<!-- Title -->
<TextView
android:id="@+id/widget_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="14sp"
android:textStyle="bold"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone"/>
<!-- Subtitle -->
<TextView
android:id="@+id/widget_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="@android:color/white"
android:textSize="12sp"
android:maxLines="1"
android:ellipsize="end"
android:visibility="gone"/>
</LinearLayout>
<!-- Placeholder View (when no image available) -->
<LinearLayout
android:id="@+id/widget_placeholder_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="@color/widget_placeholder_bg">
<ImageView
android:id="@+id/widget_placeholder"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_people_widget"
android:scaleType="fitCenter" />
<TextView
android:id="@+id/widget_placeholder_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Go to Settings -> General to customise the widget"
android:textSize="12sp"
android:gravity="center_horizontal"
android:textColor="@color/widget_text_color"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:layout_marginTop="12dp"/>
</LinearLayout>
</FrameLayout>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="100dp"
android:minHeight="100dp"
android:updatePeriodMillis="900000"
android:initialLayout="@layout/albums_widget_layout"
android:previewImage="@drawable/albums_widget_preview"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>

View File

@@ -1,5 +1,5 @@
<network-security-config>
<base-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="100dp"
android:minHeight="100dp"
android:updatePeriodMillis="900000"
android:initialLayout="@layout/people_widget_layout"
android:previewImage="@drawable/people_widget_preview"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen">
</appwidget-provider>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.668 3.33073V13.3307H6.66797V3.33073H16.668ZM16.668 1.66406H6.66797C5.7513 1.66406 5.0013 2.41406 5.0013 3.33073V13.3307C5.0013 14.2474 5.7513 14.9974 6.66797 14.9974H16.668C17.5846 14.9974 18.3346 14.2474 18.3346 13.3307V3.33073C18.3346 2.41406 17.5846 1.66406 16.668 1.66406ZM9.58464 9.7224L10.993 11.6057L13.0596 9.0224L15.8346 12.4974H7.5013L9.58464 9.7224ZM1.66797 4.9974V16.6641C1.66797 17.5807 2.41797 18.3307 3.33464 18.3307H15.0013V16.6641H3.33464V4.9974H1.66797Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="1" width="15" height="18" rx="1.4" stroke="black" stroke-width="1.4"/>
<path d="M11.773 16.5745C11.5071 16.8091 11.0978 16.8091 10.8319 16.5711L10.7934 16.5371C8.95659 14.9221 7.75655 13.8646 7.80203 12.5454C7.82302 11.9674 8.12741 11.4132 8.62072 11.0868C9.54436 10.4748 10.6849 10.7604 11.3007 11.4608C11.9164 10.7604 13.057 10.4714 13.9807 11.0868C14.474 11.4132 14.7783 11.9674 14.7993 12.5454C14.8483 13.8646 13.6448 14.9221 11.808 16.5439L11.773 16.5745Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 595 B

View File

@@ -0,0 +1,3 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.3333 3.33073H15.5V1.66406H13.8333V3.33073H7.16667V1.66406H5.5V3.33073H4.66667C3.74167 3.33073 3 4.08073 3 4.9974V16.6641C3 17.5807 3.74167 18.3307 4.66667 18.3307H16.3333C17.25 18.3307 18 17.5807 18 16.6641V4.9974C18 4.08073 17.25 3.33073 16.3333 3.33073ZM16.3333 16.6641H4.66667V8.33073H16.3333V16.6641ZM16.3333 6.66406H4.66667V4.9974H16.3333V6.66406ZM6.33333 9.9974H10.5V14.1641H6.33333V9.9974Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.9987 5.0026C10.9154 5.0026 11.6654 5.7526 11.6654 6.66927C11.6654 7.58594 10.9154 8.33594 9.9987 8.33594C9.08203 8.33594 8.33203 7.58594 8.33203 6.66927C8.33203 5.7526 9.08203 5.0026 9.9987 5.0026ZM9.9987 13.3359C12.2487 13.3359 14.832 14.4109 14.9987 15.0026H4.9987C5.19036 14.4026 7.75703 13.3359 9.9987 13.3359ZM9.9987 3.33594C8.15703 3.33594 6.66536 4.8276 6.66536 6.66927C6.66536 8.51094 8.15703 10.0026 9.9987 10.0026C11.8404 10.0026 13.332 8.51094 13.332 6.66927C13.332 4.8276 11.8404 3.33594 9.9987 3.33594ZM9.9987 11.6693C7.7737 11.6693 3.33203 12.7859 3.33203 15.0026V16.6693H16.6654V15.0026C16.6654 12.7859 12.2237 11.6693 9.9987 11.6693Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 781 B

View File

@@ -0,0 +1,11 @@
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_38630_146929)">
<path d="M11.1613 17.0685C10.5815 17.5949 9.68898 17.5949 9.10919 17.0609L9.02528 16.9846C5.0202 13.3609 2.40355 10.9884 2.50272 8.02846C2.5485 6.73158 3.2122 5.4881 4.28784 4.75574C6.30183 3.38257 8.78879 4.02339 10.1314 5.5949C11.4741 4.02339 13.9611 3.37494 15.975 4.75574C17.0507 5.4881 17.7144 6.73158 17.7602 8.02846C17.867 10.9884 15.2427 13.3609 11.2376 16.9998L11.1613 17.0685Z" stroke="black" stroke-width="1.5"/>
<path d="M14.5938 0.34668C14.9459 -0.282495 15.8744 -0.282502 16.2266 0.34668L16.293 0.491211L16.9727 2.34277L18.8252 3.02344L18.9697 3.08984C19.5989 3.44199 19.5989 4.37051 18.9697 4.72266L18.8252 4.78906L16.9727 5.46875L16.293 7.32129C16.01 8.09135 14.971 8.13983 14.5938 7.46582L14.5273 7.32129L13.8467 5.46875L11.9951 4.78906C11.1738 4.4873 11.1738 3.3252 11.9951 3.02344L13.8467 2.34277L14.5273 0.491211L14.5938 0.34668Z" fill="black" stroke="#F5F5F5" stroke-width="1.25366"/>
</g>
<defs>
<clipPath id="clip0_38630_146929">
<rect width="20" height="20" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,36 @@
ente is a simple app to backup and share your photos and videos.
If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner.
ente also makes it simple to share your albums with your loved ones, even if they aren't on ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app.
Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
We are here to make the safest photos app ever, come join our journey!
FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Collaborative albums, so you can pool together photos after a trip
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password
- Ability to free up space, by removing files that have been safely backed up
- Human support, because you're worth it
- Descriptions, so you can caption your memories and find them easily
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from Google, Apple, your hard drive and more
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
PERMISSIONS
ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.

View File

@@ -0,0 +1 @@
ente is an end-to-end encrypted photo storage app

View File

@@ -0,0 +1 @@
ente - encrypted photo storage

View File

@@ -0,0 +1,33 @@
Ente is a simple app to automatically backup and organize your photos and videos.
If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
We have apps across all platforms, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
We are here to make the safest photos app ever, come join our journey!
FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password and set to expire
- Ability to free up space, by removing files that have been safely backed up
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from all major storage providers
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
TERMS
https://ente.io/terms

View File

@@ -0,0 +1 @@
photos,photography,family,privacy,cloud,backup,videos,photo,encryption,storage,album,alternative

View File

@@ -0,0 +1 @@
Ente Photos

View File

@@ -0,0 +1 @@
Encrypted photo storage

View File

@@ -0,0 +1,30 @@
Ente is a simple app to automatically backup and organize your photos and videos.
If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
We have apps across Android, iOS, web and Desktop, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
We are here to make the safest photos app ever, come join our journey!
✨ FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password and set to expire
- Ability to free up space, by removing files that have been safely backed up
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from Google, Apple, your hard drive and more
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
💲 PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
🙋 SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.

View File

@@ -0,0 +1 @@
Encrypted photo storage - backup, organize and share your photos and videos

View File

@@ -0,0 +1 @@
Ente Photos

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AlbumsWidgetDefault.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "AlbumsWidgetPreview.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info": {
"author": "xcode",
"version": 1
}
}

View File

@@ -0,0 +1,261 @@
//
// EnteAlbumWidget.swift
// EnteAlbumWidget
import SwiftUI
import UIKit
import WidgetKit
private let widgetGroupId = "group.io.ente.frame.EnteMemoryWidget"
struct Provider: TimelineProvider {
let minutes = 15
let data = UserDefaults(suiteName: widgetGroupId)
func placeholder(in _: Context) -> FileEntry {
FileEntry(
date: Date(), index: nil, imageData: nil, title: "Title", subTitle: "Sub Title",
generatedId: nil, mainKey: nil)
}
func getSnapshot(in _: Context, completion: @escaping (FileEntry) -> Void) {
let entry = FileEntry(
date: Date(), index: -2, imageData: nil, title: "Favorites",
subTitle: "May 3, 2021",
generatedId: nil, mainKey: nil)
completion(entry)
}
func getTimeline(in _: Context, completion: @escaping (Timeline<Entry>) -> Void) {
var entries: [FileEntry] = []
// Generate a timeline consisting of five entries an hour apart, starting from the current date.
let currentDate = Calendar.current.nextDate(
after: Date(), matching: DateComponents(second: 0), matchingPolicy: .nextTime,
direction: .backward
)!
var totalAlbums =
data?.integer(forKey: "totalAlbums")
if totalAlbums != nil && totalAlbums! > 0 {
let count = totalAlbums! > 5 ? 5 : totalAlbums
for offset in 0..<count! {
let randomInt = Int.random(in: 0..<totalAlbums!)
let entryDate = Calendar.current.date(
byAdding: .minute, value: minutes * offset, to: currentDate
)!
let imageData =
data?.string(forKey: "albums_widget_" + String(randomInt))
let dictionary = data?.dictionary(
forKey: "albums_widget_" + String(randomInt) + "_data")
let generatedId = dictionary?["generatedId"] as? Int
let subTitle = dictionary?["subText"] as? String
let title = dictionary?["title"] as? String
let mainKey = dictionary?["mainKey"] as? String
let entry = FileEntry(
date: entryDate, index: randomInt, imageData: imageData, title: title,
subTitle: subTitle, generatedId: generatedId, mainKey: mainKey)
entries.append(entry)
}
} else {
let entry = FileEntry(
date: Date(), index: -1, imageData: nil, title: nil, subTitle: nil,
generatedId: nil, mainKey: nil
)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
// func relevances() async -> WidgetRelevances<Void> {
// // Generate a list containing the contexts this widget is relevant in.
// }
}
struct FileEntry: TimelineEntry {
let date: Date
let index: Int?
let imageData: String?
let title: String?
let subTitle: String?
var generatedId: Int?
var mainKey: String?
}
struct EnteAlbumWidgetEntryView: View {
var entry: Provider.Entry
let data = UserDefaults.init(suiteName: widgetGroupId)
var body: some View {
GeometryReader { geometry in
ZStack {
if let imageData = entry.imageData,
let uiImage = UIImage(contentsOfFile: imageData)
{
Image(uiImage: uiImage)
.resizable()
.backwardWidgetFullColorRenderingMode()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.height)
.overlay(
LinearGradient(
gradient: Gradient(colors: [Color.black.opacity(0.7), Color.clear]),
startPoint: .bottom,
endPoint: .top
)
.frame(height: geometry.size.height * 0.4)
.frame(maxHeight: .infinity, alignment: .bottom)
.backwardWidgetAccentable(true)
)
.overlay(
VStack(alignment: .leading, spacing: 2) {
Text(entry.title ?? "").font(
.custom("Inter", size: 14, relativeTo: .caption)
) // Custom with fallback
.bold()
.foregroundStyle(.white)
.shadow(radius: 20)
Text(entry.subTitle ?? "")
.font(.custom("Inter", size: 12, relativeTo: .caption2))
.foregroundStyle(.white)
.shadow(radius: 20)
}
.padding(.leading, geometry.size.width * 0.05)
.padding(.bottom, geometry.size.height * 0.05),
alignment: .bottomLeading
)
} else if entry.index == -2 {
if let uiImage = UIImage(named: "AlbumsWidgetPreview") {
Image(uiImage: uiImage)
.resizable()
.backwardWidgetFullColorRenderingMode()
.aspectRatio(contentMode: .fill)
.frame(width: geometry.size.width, height: geometry.size.height)
.overlay(
LinearGradient(
gradient: Gradient(colors: [
Color.black.opacity(0.7), Color.clear,
]),
startPoint: .bottom,
endPoint: .top
)
.frame(height: geometry.size.height * 0.4)
.frame(maxHeight: .infinity, alignment: .bottom)
.backwardWidgetAccentable(true)
)
.overlay(
VStack(alignment: .leading, spacing: 2) {
Text(entry.title ?? "").font(
.custom("Inter", size: 14, relativeTo: .caption)
) // Custom with fallback
.bold()
.foregroundStyle(.white)
.shadow(radius: 20)
Text(entry.subTitle ?? "")
.font(.custom("Inter", size: 12, relativeTo: .caption2))
.foregroundStyle(.white)
.shadow(radius: 20)
}
.padding(.leading, geometry.size.width * 0.05)
.padding(.bottom, geometry.size.height * 0.05),
alignment: .bottomLeading
)
}
} else if let uiImage = UIImage(named: "AlbumsWidgetDefault") {
VStack(spacing: 8) {
Spacer()
Image(uiImage: uiImage)
.resizable()
.backwardWidgetFullColorRenderingMode()
.aspectRatio(contentMode: .fit)
.padding(8)
Text("Go to Settings -> General to customise the widget")
.font(.custom("Inter", size: 12, relativeTo: .caption))
.foregroundStyle(.white) // Tint-aware color
.multilineTextAlignment(.center)
.padding(.bottom, 12)
.padding(.horizontal, 8)
.backwardWidgetAccentable(true)
Spacer()
}
.frame(width: geometry.size.width, height: geometry.size.height)
} else {
Color.gray
}
}
.clipped()
.edgesIgnoringSafeArea(.all)
.widgetURL(
URL(
string:
"albumwidget://message?generatedId=\(entry.generatedId != nil ? String(entry.generatedId!) : "nan")&mainKey=\(entry.mainKey != nil ? entry.mainKey! : "nan")&homeWidget"
)
)
}
}
}
struct EnteAlbumWidget: Widget {
let kind: String = "EnteAlbumWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
if #available(iOS 17.0, *) {
EnteAlbumWidgetEntryView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
} else {
EnteAlbumWidgetEntryView(entry: entry)
.padding()
.background()
}
}
.configurationDisplayName("Albums")
.description("See photos from selected albums including your favorites")
.contentMarginsDisabled()
}
}
#Preview(as: .systemSmall) {
EnteAlbumWidget()
} timeline: {
FileEntry(
date: .now, index: -2, imageData: nil, title: nil, subTitle: nil, generatedId: nil,
mainKey: nil)
FileEntry(
date: .now, index: -2, imageData: nil, title: nil, subTitle: nil, generatedId: nil,
mainKey: nil)
}
extension View {
@ViewBuilder
func backwardWidgetAccentable(_ accentable: Bool = true) -> some View {
if #available(iOS 16.0, *) {
self.widgetAccentable(accentable)
} else {
self
}
}
}
extension Image {
@ViewBuilder
func backwardWidgetAccentedRenderingMode(_ isAccentedRenderingMode: Bool = true) -> some View {
if #available(iOS 18.0, *) {
self.widgetAccentedRenderingMode(isAccentedRenderingMode ? .accented : .fullColor)
} else {
self
}
}
@ViewBuilder
func backwardWidgetFullColorRenderingMode() -> some View {
backwardWidgetAccentedRenderingMode(false)
}
}

View File

@@ -0,0 +1,17 @@
//
// EnteAlbumWidgetBundle.swift
// EnteAlbumWidget
//
// Created by Prateek Sunal on 5/15/25.
// Copyright © 2025 The Chromium Authors. All rights reserved.
//
import WidgetKit
import SwiftUI
@main
struct EnteAlbumWidgetBundle: WidgetBundle {
var body: some Widget {
EnteAlbumWidget()
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.io.ente.frame.EnteMemoryWidget</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "MemoriesWidgetDefault.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "MemoriesWidgetPreview.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Some files were not shown because too many files have changed in this diff Show More