Compare commits

..

70 Commits

Author SHA1 Message Date
laurenspriem
eed12c2089 Merge branch 'internal-15_06_2025' into usearch_again 2025-06-09 12:34:55 +05:30
laurenspriem
889aed6024 Bump for internal release 2025-06-09 12:34:29 +05:30
laurenspriem
ac7840cbfd Merge branch 'internal-15_06_2025' into usearch_again 2025-06-09 12:33:03 +05:30
laurenspriem
1f1304ca5b Upgrade usearch to fix Armv8-R issues 2025-06-09 12:31:38 +05:30
Neeraj Gupta
94098d8a07 Bump version 2025-06-06 12:35:30 +05:30
Neeraj Gupta
4b9c5fcb73 Merge branch 'internal-15_06_2025' of https://github.com/ente-io/auth into internal-15_06_2025 2025-06-06 12:33:48 +05:30
Neeraj Gupta
6ed16e5e02 Merge branch 'main' into internal-15_06_2025 2025-06-06 12:33:21 +05:30
laurenspriem
82a8e504af Merge branch 'internal-15_06_2025' into usearch_again 2025-06-04 22:14:26 +05:30
ashilkn
cc1660d9af bump up build number" 2025-06-04 18:24:59 +05:30
ashilkn
52b6fc108b Merge branch 'memory_improvement' into internal-15_06_2025 2025-06-04 18:19:37 +05:30
Neeraj Gupta
8b3b20aa93 Remove unsued type 2025-06-02 18:38:45 +05:30
Neeraj Gupta
408b0bfe2d Merge branch 'internal-15_06_2025' of https://github.com/ente-io/auth into internal-15_06_2025 2025-06-02 17:55:33 +05:30
Neeraj Gupta
655be76428 Bump version 2025-06-02 17:55:06 +05:30
Neeraj Gupta
9fedf8d6b7 Merge branch 'main' into internal-15_06_2025 2025-06-02 17:54:47 +05:30
ashilkn
7c4e775872 Bump build number 2025-06-02 14:56:04 +05:30
Neeraj Gupta
ecfa640c28 Bump version 2025-05-30 15:44:46 +05:30
Neeraj Gupta
1997eb20f3 Merge branch 'main' into internal-15_06_2025 2025-05-30 15:44:30 +05:30
laurenspriem
726425bbb6 Put vector db behind feature flag internal 2025-05-27 14:07:34 +05:30
laurenspriem
c068f26604 Aggressive logging of vectorDB migration 2025-05-22 11:32:36 +05:30
laurenspriem
e60c2b1192 GC hints 2025-05-22 11:19:19 +05:30
laurenspriem
beb049f817 Reduce batch size in migration 2025-05-22 10:38:43 +05:30
laurenspriem
7021c9fe02 Bump for internal release 2025-05-12 17:05:31 +05:30
laurenspriem
c2d5dece9e Merge branch 'main' into usearch_again 2025-05-12 17:04:42 +05:30
laurenspriem
b76d41b84d Specify rust version in readme 2025-05-12 15:48:27 +05:30
laurenspriem
3b9c76649d Update readme to include rust 2025-05-12 15:12:31 +05:30
laurenspriem
62ed8b6975 Log vector db stats when opening connection 2025-05-12 14:46:55 +05:30
laurenspriem
2422dba4d4 vector db more stats logging 2025-05-12 14:23:16 +05:30
laurenspriem
eb1916e3a3 integrate vector db in magic search 2025-05-12 11:39:02 +05:30
laurenspriem
df0d9137a6 Integration clip embeddings in vector db 2025-05-11 13:09:56 +05:30
laurenspriem
fc36b87965 Clip migration method 2025-05-11 13:09:20 +05:30
laurenspriem
63d90ea275 Class for vector db stats 2025-05-09 16:36:39 +05:30
laurenspriem
bb7f8a5eef More testing 2025-05-09 15:59:46 +05:30
laurenspriem
2f5a02ec43 delete table option 2025-05-09 12:57:44 +05:30
laurenspriem
d411d91966 vector db api ensure capacity safety 2025-05-09 12:56:59 +05:30
laurenspriem
54b712953a vector db api let clear include capacity reset 2025-05-09 10:49:03 +05:30
laurenspriem
27ad020adc Testing clip migration to vector DB 2025-05-08 17:40:01 +05:30
laurenspriem
ce112bd4d7 Index stats method 2025-05-08 15:23:23 +05:30
laurenspriem
2ffb73d053 Consistent method parameters 2025-05-08 15:07:50 +05:30
laurenspriem
6478d438b5 vector db api ensure never duplicate keys 2025-05-08 14:30:51 +05:30
laurenspriem
d87069eb4c vectordb api add documentation 2025-05-08 12:31:09 +05:30
laurenspriem
5447350ab1 vector db api add check for key 2025-05-08 12:29:41 +05:30
laurenspriem
ea1a2960bf First implementation of clip vector db 2025-05-08 12:08:55 +05:30
laurenspriem
832f2c451e Add bulk get method to vector db api 2025-05-08 11:47:50 +05:30
laurenspriem
715c7c23a7 Add bulk remove embeddings api 2025-05-08 10:29:25 +05:30
laurenspriem
e9c2e40a43 Update to latest usearch 2025-05-07 13:25:40 +05:30
laurenspriem
603c275c09 Update basic usearch test 2025-05-07 12:01:45 +05:30
laurenspriem
7b9d6df2fd Fix ios build issue 2025-05-07 11:32:53 +05:30
laurenspriem
a4afecef3d Fix ios config 2025-05-07 10:50:39 +05:30
laurenspriem
4d9bfb89ae macos config 2025-05-07 10:36:17 +05:30
laurenspriem
f2a74bd35e Merge branch 'main' into usearch_again 2025-05-06 15:34:59 +05:30
laurenspriem
8c65a21b86 don't generate for web 2025-04-10 13:03:52 +05:30
laurenspriem
a07e8477fb format 2025-04-09 15:34:06 +05:30
laurenspriem
8b489e9ced Give distances in bulk search 2025-04-09 15:31:03 +05:30
laurenspriem
77e2bb1d46 Stop our own vector comparisons in benchmark 2025-04-09 15:21:20 +05:30
laurenspriem
4ce24e080a logging of benchmarking 2025-04-09 14:22:39 +05:30
laurenspriem
4e5ca3dca6 Benchmark face embeddings 2025-04-09 13:43:39 +05:30
laurenspriem
2ed155ab47 ignore trailing commas in generated code 2025-04-09 13:14:26 +05:30
laurenspriem
65e71e3caf Reintroduce reset_index method 2025-04-09 10:49:50 +05:30
laurenspriem
ee5efbcfcc Don't consume index if not needed 2025-04-09 10:43:07 +05:30
laurenspriem
6cf4530f7d Remove reset index 2025-04-09 10:06:46 +05:30
laurenspriem
e6ee09ca30 Back to basic error handling 2025-04-08 17:03:41 +05:30
laurenspriem
6d2f53b86c Update usearch 2025-04-08 14:56:47 +05:30
laurenspriem
6500748c5a Make vector db stateful in rust 2025-04-08 14:48:30 +05:30
laurenspriem
120dbeb4fc Fix null pointer crash 2025-04-05 16:56:14 +05:30
laurenspriem
c42807487b Upgrade Android NDK to r28 latest stable 2025-04-05 16:11:03 +05:30
laurenspriem
e707e24da9 first integration of usearch 2025-04-05 16:10:39 +05:30
laurenspriem
af817ec439 Test rust 2025-04-04 11:49:54 +05:30
laurenspriem
ddb44d8fd7 Integrate flutter_rust_bridge 2025-03-31 15:56:03 +05:30
laurenspriem
778822b12d Upgrade sdk 2025-03-31 15:47:51 +05:30
laurenspriem
9599ec3236 dart format 2025-03-31 15:34:48 +05:30
650 changed files with 30294 additions and 27517 deletions

View File

@@ -39,13 +39,6 @@
"title": "Ankama",
"slug": "ankama"
},
{
"title": "Ankara University",
"slug": "ankara_university",
"altNames": [
"Ankara Üniversitesi"
]
},
{
"title": "Anycoin Direct",
"slug": "anycoindirect"
@@ -482,16 +475,6 @@
"title": "FreeTaxUSA",
"slug": "freetaxusa"
},
{
"title": "FZJ",
"slug": "fzj",
"hex": "023d6b",
"altNames": [
"Forschungszentrum Jülich",
"FZJ IdP",
"iffLogin"
]
},
{
"title": "G2A"
},
@@ -570,14 +553,6 @@
"Hugging Face"
]
},
{
"title": "IBKR",
"slug": "ibkr",
"altNames": [
"Interactive Brokers",
"IB"
]
},
{
"title": "IceDrive",
"slug": "ice_drive"
@@ -699,10 +674,6 @@
"飞书"
]
},
{
"title": "LaunchDarkly",
"hex": "858585"
},
{
"title": "Letterboxd"
},
@@ -767,9 +738,6 @@
"gehirneimer"
]
},
{
"title": "Memed"
},
{
"title": "Mercado Libre",
"slug": "mercado_libre",
@@ -1144,16 +1112,6 @@
"title": "Seafile",
"slug": "seafile"
},
{
"title": "SEI",
"altNames": [
"sei",
"sei!",
"SEI!",
"Sistema Eletrônico de Informações",
"SEI/SEDE"
]
},
{
"title": "Sendgrid"
},
@@ -1461,15 +1419,6 @@
"Toshl"
]
},
{
"title": "Tebex",
"slug": "tebex",
"altNames": [
"tebex",
"tebex.io",
"buycraft"
]
},
{
"title": "xAI",
"slug": "xai"
@@ -1477,9 +1426,6 @@
{
"title": "Cronometer",
"slug": "cronometer"
},
{
"title": "Zitadel"
}
]
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -1,7 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,12 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="775" height="1511" version="1.2">
<defs>
<linearGradient id="a" x1="667.4" x2="-.7" y1="1142.8" y2="1142.8"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#d81222" />
<stop offset="1" stop-color="#960b1a" />
</linearGradient>
</defs>
<path d="M.3 1510.2V775.3l668 734.9z" style="fill:url(#a)" />
<path fill="#d81222"
d="M574.2 1154.6c-110.5 0-199.9-89.5-199.9-200.2 0-110.8 89.4-200.3 199.9-200.3 110.6 0 200 89.5 200 200.3 0 110.7-89.4 200.2-200 200.2M668.3.4.3 1510.2V775.3z" />
</svg>

Before

Width:  |  Height:  |  Size: 624 B

View File

@@ -1 +0,0 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216 214.94"><path d="M109.8,214.94a4.87,4.87,0,0,1-4.26-2.66,4.5,4.5,0,0,1,.44-4.82l50.49-69.53L68,174.11a4.61,4.61,0,0,1-1.9.41,4.77,4.77,0,0,1-4.52-3.4,4.57,4.57,0,0,1,2-5.21L141.33,120,4.41,112.13a4.69,4.69,0,0,1,0-9.36l137-7.87L63.61,49a4.56,4.56,0,0,1-1.94-5.2,4.74,4.74,0,0,1,4.51-3.4,4.6,4.6,0,0,1,1.9.4L156.5,77,106,7.48a4.56,4.56,0,0,1-.44-4.83A4.84,4.84,0,0,1,109.84,0a4.59,4.59,0,0,1,3.28,1.41L213.77,102.05a7.65,7.65,0,0,1,0,10.8L113.08,213.53A4.59,4.59,0,0,1,109.8,214.94Z"/></svg>

Before

Width:  |  Height:  |  Size: 580 B

View File

@@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="129.55565mm"
height="129.41467mm"
viewBox="0 0 129.55565 129.41467"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:export-filename="memed2.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#ec9000"
inkscape:document-units="mm"><inkscape:page
x="0"
y="0"
width="129.55565"
height="129.41467"
id="page2"
margin="0"
bleed="0" /></sodipodi:namedview><defs
id="defs1" /><g
inkscape:label="Camada 1"
inkscape:groupmode="layer"
id="layer1"
transform="matrix(1.6834732,0,0,1.6834732,47.649925,-205.86142)"><path
style="fill:#f5f2f5;fill-opacity:1;stroke:none;stroke-width:1.64472;stroke-opacity:0.216306"
d="m -229.63928,666.3282 c -1.80919,-0.13135 -3.05972,0.10695 -3.33899,-0.0739 -0.8201,-0.53107 -3.56255,-6.29326 -5.5356,-10.78298 -5.66304,-12.88636 -9.6848,-29.27567 -10.13326,-42.53094 -0.19655,-5.80951 0.10184,-7.61979 0.87562,-12.19698 1.5552,-9.19979 3.41577,-14.15687 8.37613,-20.05013 4.44031,-5.2754 13.12164,-10.82873 19.15739,-12.38511 4.40045,-1.1347 11.49057,-1.83141 19.07025,-1.58315 10.46549,0.34277 15.53289,2.0447 23.98486,6.0306 8.4934,4.00545 8.5476,4.69447 17.19355,12.68346 l 9.17947,8.48198 1.73483,-3.36021 c 5.30229,-10.27007 13.54153,-11.35636 20.42059,-15.3818 9.98116,-5.84072 20.33566,-8.65915 31.655487,-8.65915 20.40721,0 35.175223,9.03215 40.949411,24.9899 2.680505,7.40793 3.691311,17.03863 2.871769,25.62379 -1.523031,15.95462 -6.502251,33.73112 -13.14355,45.38711 l -2.092079,3.67176 -9.091661,0.0949 c -10.759893,0.11232 -14.787739,0.55882 -14.782316,-0.39427 0.0037,-0.6492 2.126398,-5.62719 6.614865,-15.48568 6.460624,-14.19017 9.107566,-24.18606 9.458064,-38.19534 0.310181,-12.39792 -0.54619,-15.46077 -5.720525,-20.65311 -4.842652,-4.8595 -9.652569,-6.74545 -17.203292,-6.31247 -6.931853,0.39749 -14.431373,3.39042 -20.210693,8.03964 -8.08359,6.50292 -18.16141,19.27599 -24.59806,30.53119 -6.58062,11.50695 -5.26814,12.2378 -6.49554,11.46026 -0.4565,-0.28918 -3.06129,-4.8228 -4.93182,-8.21504 -8.50061,-15.416 -17.38274,-26.70868 -26.57306,-33.49026 -3.97121,-2.93037 -10.51147,-6.3019 -14.41859,-7.35191 -3.7439,-1.00615 -11.45838,-0.93722 -14.80652,0.0702 -6.9037,2.07788 -11.28665,6.76825 -13.35945,14.26878 -0.92563,3.34947 -1.3257,12.28307 -0.72975,18.04782 1.25223,12.1133 4.66457,22.59592 12.97979,39.84761 2.77157,5.7502 4.31011,7.49886 3.74297,7.73574 -0.79147,0.33057 -9.85373,0.57929 -14.57149,0.23675 z"
id="path8"
sodipodi:nodetypes="cssssssssscsssssscsssssssssssscsssscc"
transform="matrix(0.26458333,0,0,0.26458333,50.096465,-2.4191315)" /><path
style="display:inline;fill:#6262f5"
d="m 4.4956948,198.76332 c -3.30706,-0.44247 -8.3326,-1.84639 -9.73079,-2.71836 -0.32338,-0.20168 -0.90706,-0.44056 -1.29707,-0.53085 -0.39,-0.0903 -0.87471,-0.32978 -1.07713,-0.5322 -0.20241,-0.20241 -0.9589,-0.66744 -1.68108,-1.0334 -4.5630398,-2.31227 -11.4938598,-9.24939 -13.7898298,-13.80238 -0.3694,-0.73252 -0.7455,-1.39139 -0.83579,-1.46415 -0.0903,-0.0728 -0.29661,-0.42994 -0.4585,-0.79375 -0.16189,-0.3638 -0.55469,-1.22236 -0.87289,-1.90791 -1.88451,-4.06016 -3.16441,-10.69837 -3.05006,-15.81917 0.11739,-5.25717 1.3037,-10.99832 3.05006,-14.76084 0.3182,-0.68555 0.711,-1.54411 0.87289,-1.90791 0.16189,-0.3638 0.36821,-0.72099 0.4585,-0.79375 0.0903,-0.0728 0.46639,-0.73162 0.83579,-1.46414 2.26147,-4.48457 8.57756,-10.84773 13.5529798,-13.65397 1.01864,-0.57454 2.03068,-1.15388 2.24896,-1.28742 2.24204,-1.37166 8.36495,-3.26099 12.03854,-3.71469 2.47599,-0.3058 8.3413402,-0.40086 9.5250002,-0.15438 0.29104,0.0606 1.3626,0.20579 2.38125,0.32263 2.01526,0.23115 1.93948,0.21791 2.51354,0.43921 1.02205,0.39401 3.03969,0.99193 3.34719,0.99193 0.18838,0 0.97085,0.2881 1.73881,0.64022 0.76796,0.35212 1.93207,0.88513 2.58692,1.18446 0.65484,0.29934 1.54791,0.75538 1.98458,1.01343 0.43668,0.25805 1.38918,0.81695 2.11667,1.242 1.79243,1.04728 3.18453,2.21035 6.20039,5.1803 2.79182,2.74933 4.23282,4.45383 5.48018,6.48229 3.17204,5.1584 5.07219,10.30587 5.66481,15.34584 0.55918,4.7556 0.47529,7.60255 -0.40153,13.62604 -0.18126,1.24527 -1.56492,5.54247 -2.28224,7.08792 -0.3182,0.68555 -0.71099,1.54411 -0.87288,1.90791 -0.16189,0.36381 -0.36822,0.72099 -0.45851,0.79375 -0.0903,0.0728 -0.46639,0.73163 -0.83578,1.46415 -2.30965,4.5801 -9.11275,11.36702 -13.94307,13.9099 -0.8041,0.42331 -1.64059,0.88054 -1.85887,1.01607 -1.11841,0.6944 -4.14314,1.88062 -6.61459,2.59408 -5.20986,1.50399 -10.74885,1.87148 -16.5364502,1.09714 z m -9.6573,-24.95559 c 0,-0.0615 -0.57847,-1.28185 -1.28549,-2.71198 -2.18367,-4.41702 -3.20781,-8.24456 -3.20895,-11.99293 -0.001,-4.42964 2.01227,-6.72114 5.88783,-6.70038 4.47803005,0.024 9.21844,4.21886 13.02766,11.52839 0.43587,0.8364 0.8697302,1.52101 0.9641202,1.52136 0.0944,3.4e-4 0.55697,-0.74351 1.02794,-1.65302 2.22999,-4.30635 6.18881,-8.96011 8.72108,-10.25198 3.33434,-1.70105 6.32476,-1.57437 8.3288,0.35282 1.52052,1.46222 1.82726,2.64731 1.65484,6.39343 -0.15794,3.4314 -1.07983,6.63419 -3.10868,10.8001 -0.86224,1.77047 -1.16282,2.63732 -0.94364,2.72142 0.1769,0.0679 1.62162,0.0894 3.21049,0.0478 l 2.88886,-0.0756 0.64276,-1.1663 c 2.06367,-3.7446 3.65793,-10.57405 3.37181,-14.44411 -0.4585,-6.20174 -3.63562,-9.71814 -9.48799,-10.50118 -5.6749,-0.75929 -11.4014,1.60585 -15.29697,6.31791 -0.50399,0.60962 -0.97587,1.1084 -1.04863,1.1084 -0.0728,0 -0.5490102,-0.5047 -1.0583402,-1.12155 -2.23259,-2.70394 -5.45587,-4.88484 -8.73124995,-5.90763 -1.99626005,-0.62336 -6.74195005,-0.63537 -8.73812005,-0.0221 -1.9981998,0.61389 -4.1480898,2.17983 -5.3452198,3.89337 -1.1067,1.58409 -1.39833,2.42025 -1.82847,5.24242 -0.55493,3.64096 0.82065,10.37308 3.01361,14.7487 0.43759,0.87312 0.92909,1.67497 1.09223,1.78189 0.30781,0.20173 6.2497198,0.288 6.2497198,0.0907 z"
id="path1"
sodipodi:nodetypes="ssssssssssssscssssssssssssssssssssssscscssscsssssccsssssssssssscc" /></g></svg>

Before

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,19 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
<title>vikunja</title>
<defs>
<clipPath clipPathUnits="userSpaceOnUse" id="cp1">
<path d="m211.62 166.19v47.1h-167.62v-47.1z"/>
</clipPath>
</defs>
<style>
.s0 { fill: #101010 }
.s1 { fill: #ffffff }
</style>
<path class="s0" d="m226.9 46.8q-0.8-0.9-1.6-1.9-0.8-1-1.6-1.9-0.9-1-1.7-1.9-0.9-0.9-1.7-1.9c-24.1-25.2-56.9-39.2-92.3-39.2-8.3 0-16.7 0.8-24.9 2.4-8.3 1.7-16.3 4.1-24.1 7.3-7.7 3.2-15.1 7.2-22.1 11.9-7 4.6-13.4 10-19.4 15.9-5.9 6-11.3 12.4-15.9 19.4-4.7 7-8.7 14.4-11.9 22.1-3.2 7.8-5.6 15.8-7.3 24.1-1.6 8.2-2.4 16.6-2.4 24.9 0 8.4 0.8 16.8 2.5 25 1.6 8.3 4 16.3 7.2 24.1 3.2 7.7 7.2 15.1 11.9 22.1 4.6 7 10 13.4 15.9 19.4 6 5.9 12.4 11.3 19.4 15.9 7 4.7 14.4 8.7 22.1 11.9 7.8 3.2 15.8 5.6 24.1 7.2 8.2 1.7 16.6 2.5 24.9 2.4 8.4 0.1 16.8-0.7 25-2.4 8.3-1.6 16.3-4 24-7.2 7.8-3.2 15.2-7.2 22.2-11.9 6.9-4.6 13.4-10 19.3-15.9 6-6 11.3-12.4 16-19.4 4.7-7 8.7-14.4 11.9-22.1 3.2-7.8 5.6-15.8 7.2-24.1 1.7-8.2 2.5-16.6 2.4-25 0-29.2-10.5-58.2-29.1-81.2"/>
<g id="Clip-Path" clip-path="url(#cp1)">
<g>
<path fill-rule="evenodd" class="s1" d="m117.5 183.8c2.5-3.5 6.5-5.5 11.3-5.5 9.1 0 15.6 7 15.6 16.7 0 9.8-6.6 16.8-15.8 16.8-4.8 0-8.7-1.8-11.2-5.3l-0.2 4.5h-6.9v-44.8h7.2zm20.2 11.4c0-5.7-4.1-10.4-9.3-10.4-5.2 0-9.4 4.7-9.4 10.4 0 5.8 4.2 10.5 9.4 10.5 5.2 0 9.3-4.7 9.3-10.5zm-70.4 9.4l0.3-0.2 2.8 5.7-0.1 0.1c-2.7 2.1-5.6 3.1-8.8 3.1-7 0-10.9-4-10.9-11.4v-16.7h-6.6l1.6-6.5h5v-8.3h7.3v8.3h11.6v6.5h-11.6v16.7c0 2.9 1.5 4.6 4.3 4.6 1.9 0 3.3-0.5 5.1-1.9zm36.1-9.8q0 0.5-0.1 0.9 0 0.4 0 0.8v0.3h-23.7c0.7 5.6 4.1 8.6 9.5 8.6 3.5 0 6.3-1.2 8.9-3.8l0.2-0.2 3.8 4.7-0.1 0.1c-3.4 3.8-7.6 5.5-13.2 5.5-9.9 0-16.3-6.6-16.3-16.8 0-9.7 6.7-16.8 16-16.8 9.1 0 15 6.6 15 16.7zm-7.3-2.7c-0.6-4.8-3.5-7.6-7.8-7.6-4.3 0-7.6 3-8.5 7.6zm82.8 2.7q0 0.5 0 0.9-0.1 0.4-0.1 0.8v0.3h-23.7c0.7 5.6 4.1 8.6 9.5 8.6 3.5 0 6.3-1.2 8.9-3.8l0.2-0.2 3.9 4.7-0.2 0.1c-3.4 3.8-7.6 5.5-13.2 5.5-9.9 0-16.2-6.6-16.2-16.8 0-9.7 6.6-16.8 15.9-16.8 9.1 0 15 6.6 15 16.7zm-23.6-2.7h16.3c-0.6-4.9-3.4-7.6-7.8-7.6-4.3 0-7.6 3-8.5 7.6zm55-13.4l-11.2 15.4 12.5 16.9h-8.6l-8.1-11.5-7.8 11.5h-8.2l12.1-16.5-11.5-15.8h8.5l7.1 10.5 7-10.5z"/>
</g>
</g>
<path class="s1" d="m130.9 71.8c4.5-7.3 12.5-9.5 12.5-9.5 0 0-15.3-4-15.3-20.3 0 16.3-15.3 20.3-15.3 20.3 0 0 8 2.2 12.4 9.5h-20.9v28.2l4.8-8.7h9.5v48.6l19 19.4v-59.5c-4.9-2.3-11.8-8.1-14.3-12.5 4.2 1.3 10 3 14.4 4h14.2v-19.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 467 467" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="zitadel-logo-solo-darkdesign" transform="matrix(0.564847,0,0,0.659318,-1282.85,0)">
<rect x="2271.15" y="0" width="826.773" height="708.241" style="fill:none;"/>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5923.46,-2258.26)">
<path d="M1493.5,1056.38L1493.5,1037L1496.5,1037L1496.5,1061.62L1426.02,1020.38L1496.5,979.392L1496.5,1004L1493.5,1004L1493.5,984.608L1431.98,1020.39L1493.5,1056.38Z" style="fill:white;"/>
</g>
<g transform="matrix(31.0036,0,0,15.0393,-397275,-666.457)">
<g transform="matrix(-0.0429306,-0.282967,0.160219,-0.0758207,12884.5,137.392)">
<path d="M212.517,110L200.392,110L190,92L179.608,110L167.483,110L190,71L212.517,110Z" style="fill:url(#_Linear1);"/>
</g>
<g transform="matrix(0.160219,0.0758207,-0.0429306,0.282967,12878.9,10.8747)">
<path d="M212.517,110L200.392,110L190,92L179.608,110L167.483,110L190,71L212.517,110Z" style="fill:url(#_Linear2);"/>
</g>
<g transform="matrix(-0.117289,0.207146,-0.117289,-0.207146,12943.8,65.7)">
<path d="M212.517,110L200.392,110L190,92L179.608,110L167.483,110L190,71L212.517,110Z" style="fill:url(#_Linear3);"/>
</g>
<g transform="matrix(-0.160219,-0.0758207,0.0429306,-0.282967,12917.4,132.195)">
<path d="M139.622,117L149,142L130.244,142L139.622,117Z" style="fill:url(#_Linear4);"/>
</g>
<g transform="matrix(-0.117289,0.207146,0.117289,0.207146,12897.8,5.87512)">
<path d="M139.622,117L149,142L130.244,142L139.622,117Z" style="fill:url(#_Linear5);"/>
</g>
<g transform="matrix(-0.0429306,-0.282967,-0.160219,0.0758207,12936.8,97.6441)">
<path d="M139.622,117L149,142L130.244,142L139.622,117Z" style="fill:url(#_Linear6);"/>
</g>
</g>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5928.43,-2257.12)">
<circle cx="1496" cy="1004" r="7" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5884.5,-2116.69)">
<circle cx="1496" cy="1004" r="7" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5855.22,-2023.06)">
<circle cx="1496" cy="1004" r="7" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-6234.47,-2112.14)">
<circle cx="1496" cy="1004" r="7" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.331,4.25561,-5957.71,-2350.75)">
<circle cx="1496" cy="1004" r="7" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.16463,3.72366,-5477.99,-831.33)">
<path d="M1499.26,757.787C1499.26,757.787 1497.37,756.489 1497,755.2C1496.71,754.182 1496.57,750.662 1496.54,750C1496.41,747.303 1499.21,745.644 1499.21,745.644L1490.01,745.835C1490.01,745.835 1493.15,745.713 1493.46,750C1493.51,750.661 1493.23,753.476 1493,755.2C1492.91,756.447 1491.2,757.668 1491.2,757.668L1499.26,757.787Z" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.16463,3.72366,-5404.79,-597.271)">
<path d="M1495,760L1495,744" style="fill:none;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.16463,3.72366,-5404.79,-597.271)">
<path d="M1498.27,757.077C1498.27,757.077 1496.71,756.46 1496.65,754.8C1496.65,753.658 1496.64,753.281 1496.65,752.016C1496.62,751.334 1496.59,750.608 1496.65,749.949C1496.78,746.836 1498.5,746.156 1498.5,746.156L1491.46,745.931C1491.46,745.931 1493.37,746.719 1493.65,749.83C1493.71,750.489 1493.69,751.528 1493.65,752.209C1493.64,753.331 1493.64,753.413 1493.65,754.518C1493.68,756.334 1492.58,756.827 1492.58,756.827L1498.27,757.077Z" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.16463,3.72366,-5770.62,-677.495)">
<path d="M1496.17,759.473L1555.54,720.014" style="fill:none;"/>
</g>
<g transform="matrix(4.96737,-1.14029,1.16463,3.72366,-5770.62,-677.495)">
<path d="M1500.86,762.056C1500.86,762.056 1499.86,760.4 1503.09,757.456C1504.91,755.797 1507.33,754.151 1509.98,752.255C1514.82,748.79 1520.68,744.94 1526.52,741.049C1531.45,737.766 1536.38,734.479 1540.82,731.68C1544.52,729.349 1547.85,727.296 1550.54,725.8C1551.07,725.506 1551.6,725.329 1552.05,725.029C1554.73,723.257 1556.85,724.968 1556.85,724.968L1552.23,716.282C1552.23,716.282 1551.99,719.454 1550,720.997C1549.57,721.333 1549.15,721.741 1548.67,722.12C1546.2,724.053 1542.99,726.344 1539.39,728.867C1535.06,731.898 1530.13,735.166 1525.19,738.438C1519.35,742.314 1513.52,746.234 1508.49,749.329C1505.74,751.023 1503.28,752.577 1501.13,753.598C1497.99,755.086 1495.28,753.617 1495.28,753.617L1500.86,762.056Z" style="fill:white;"/>
</g>
<g transform="matrix(4.96737,-1.14029,-1.16463,-3.72366,-3997,4993.28)">
<path d="M1496.17,759.473L1555.54,720.014" style="fill:none;"/>
</g>
<g transform="matrix(4.96737,-1.14029,-1.16463,-3.72366,-3997,4993.28)">
<path d="M1496.1,754.362C1496.1,754.362 1497.2,755.607 1501.13,753.598C1503.25,752.509 1505.74,751.023 1508.49,749.329C1513.52,746.234 1519.35,742.314 1525.19,738.438C1530.13,735.166 1534.94,731.832 1539.27,728.802C1542.87,726.279 1549.36,722.059 1549.81,721.75C1552.75,719.73 1552.18,718.196 1552.18,718.196L1555.28,724.152C1555.28,724.152 1553.77,722.905 1551.37,724.681C1550.93,725.006 1544.52,729.349 1540.82,731.68C1536.38,734.479 1531.45,737.766 1526.52,741.049C1520.68,744.94 1514.82,748.79 1509.98,752.255C1507.33,754.151 1504.89,755.771 1503.09,757.456C1499.47,760.841 1501.26,763.283 1501.26,763.283L1496.1,754.362Z" style="fill:white;"/>
</g>
</g>
<defs>
<linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-41.5984,155.247,-155.247,-41.5984,201.516,76.8392)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(155.247,-41.5984,41.5984,155.247,110.08,195.509)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-113.649,-113.649,113.649,-113.649,258.31,215.618)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-155.247,41.5984,-41.5984,-155.247,220.914,144.546)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-113.649,113.649,113.649,113.649,206.837,124.661)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
<linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-41.5984,-155.247,-155.247,41.5984,152.054,262.8)"><stop offset="0" style="stop-color:rgb(255,143,0);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(254,0,255);stop-opacity:1"/></linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -14,6 +14,7 @@ import 'package:ente_auth/models/key_gen_result.dart';
import 'package:ente_auth/models/private_key_attributes.dart';
import 'package:ente_auth/store/authenticator_db.dart';
import 'package:ente_auth/utils/directory_utils.dart';
import 'package:ente_auth/utils/lock_screen_settings.dart';
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:logging/logging.dart';
@@ -32,6 +33,7 @@ class Configuration {
static const emailKey = "email";
static const keyAttributesKey = "key_attributes";
static const keyShouldShowLockScreen = "should_show_lock_screen";
static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
static const keyKey = "key";
static const secretKeyKey = "secret_key";
@@ -131,6 +133,7 @@ class Configuration {
key: key,
);
}
await LockScreenSettings.instance.removePinAndPassword();
await AuthenticatorDB.instance.clearTable();
_key = null;
_cachedToken = null;
@@ -465,6 +468,24 @@ class Configuration {
await _preferences.setBool(hasOptedForOfflineModeKey, true);
}
Future<bool> shouldShowLockScreen() async {
final bool isPin = await LockScreenSettings.instance.isPinSet();
final bool isPass = await LockScreenSettings.instance.isPasswordSet();
return isPin || isPass || shouldShowSystemLockScreen();
}
bool shouldShowSystemLockScreen() {
if (_preferences.containsKey(keyShouldShowLockScreen)) {
return _preferences.getBool(keyShouldShowLockScreen)!;
} else {
return false;
}
}
Future<void> setSystemLockScreen(bool value) {
return _preferences.setBool(keyShouldShowLockScreen, value);
}
void setVolatilePassword(String volatilePassword) {
_volatilePassword = volatilePassword;
}

View File

@@ -8,7 +8,7 @@
},
"onBoardingBody": "Бяспечна зрабіць рэзервовую копію кодаў 2ФА",
"onBoardingGetStarted": "Пачаць",
"setupFirstAccount": "Наладзіць свой першы ўліковы запіс",
"setupFirstAccount": "Наладзіць ваш першы ўліковы запіс",
"importScanQrCode": "Сканіраваць код QR-код",
"qrCode": "QR-код",
"importEnterSetupKey": "Увесці ключ наладжвання",
@@ -45,38 +45,19 @@
"timeBasedKeyType": "Заснаваныя на часе (TOTP)",
"counterBasedKeyType": "Заснаваныя на лічыльніку (HOTP)",
"saveAction": "Захаваць",
"nextTotpTitle": "далей",
"nextTotpTitle": "наступны",
"deleteCodeTitle": "Выдаліць код?",
"deleteCodeMessage": "Вы сапраўды хочаце выдаліць гэты код? Гэта дзеянне з'яўляецца незваротным.",
"trashCode": "Выдаліць код?",
"trashCodeMessage": "Вы сапраўды хочаце выдаліць код для {account}?",
"trash": "Сметніца",
"viewLogsAction": "Паглядзець журналы",
"preparingLogsTitle": "Падрыхтоўка журналаў...",
"viewLogsAction": "Паглядзець журнал",
"preparingLogsTitle": "Падрыхтоўка журнала...",
"emailLogsTitle": "Адправіць журнал па электроннай пошце",
"emailLogsMessage": "Адпраўце журналы на {email}",
"@emailLogsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"copyEmailAction": "Скапіяваць электронную пошту",
"exportLogsAction": "Экспартаваць журналы",
"reportABug": "Паведаміць аб памылцы",
"crashAndErrorReporting": "Справаздачы аб збоях і памылках",
"reportBug": "Паведаміць аб памылцы",
"emailUsMessage": "Адпраўце нам ліст на {email}",
"@emailUsMessage": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"exportLogsAction": "Экспартаваць журнал",
"reportABug": "Паведаміць пра памылку",
"reportBug": "Паведаміць пра памылку",
"contactSupport": "Звярнуцца ў службу падтрымкі",
"rateUsOnStore": "Ацаніць нас у {storeName}",
"blog": "Блог",
"verifyPassword": "Праверыць пароль",
"pleaseWait": "Пачакайце...",
@@ -85,184 +66,32 @@
"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": "Калі ласка, паспрабуйце яшчэ раз",
"existingUser": "Існуючы карыстальнік",
"newUser": "Навічок у Ente",
"delete": "Выдаліць",
"enterYourPasswordHint": "Увядзіце ваш пароль",
"forgotPassword": "Забылі пароль",
"oops": "Вой",
"faq": "Частыя пытанні",
"leaveFamily": "Пакінуць сямейны план",
"scan": "Сканіраваць",
"scanACode": "Сканіраваць код",
"verify": "Праверыць",
"verifyEmail": "Праверыць электронную пошту",
"lostDeviceTitle": "Згубілі прыладу?",
"verifyPasskey": "Праверыць ключ доступу",
"loginWithTOTP": "Увайсці з TOTP",
"recoverAccount": "Аднавіць уліковы запіс",
"recover": "Аднавіць",
"invalidQRCode": "Памылковы QR-код",
"deleteAccount": "Выдаліць уліковы запіс",
"noDeleteAccountAction": "Не, выдаліць уліковы запіс",
"sendEmail": "Адправіць ліст",
"createNewAccount": "Стварыць новы ўліковы запіс",
"weakStrength": "Ненадзейны",
"strongStrength": "Надзейны",
"moderateStrength": "Умераная",
"confirmPassword": "Пацвердзіць пароль",
"close": "Закрыць",
"oopsSomethingWentWrong": "Штосьці пайшло не так.",
"selectLanguage": "Выберыце мову",
"language": "Мова",
"social": "Сацыяльныя сеткі",
"security": "Бяспека",
"lockscreen": "Экран блакіроўкі",
"searchHint": "Пошук...",
"search": "Пошук",
"noResult": "Няма вынікаў",
"addCode": "Дадаць код",
"scanAQrCode": "Сканіраваць QR-код",
"edit": "Рэдагаваць",
"share": "Абагуліць",
"shareCodes": "Абагуліць коды",
"restore": "Аднавіць",
"error": "Памылка",
"doThisLater": "Зрабіць гэта пазней",
"saveKey": "Захаваць ключ",
"save": "Захаваць",
"send": "Адправіць",
"back": "Назад",
"createAccount": "Стварыць уліковы запіс",
"password": "Пароль",
"privacyPolicyTitle": "Палітыка прыватнасці",
"termsOfServicesTitle": "Умовы",
"encryption": "Шыфраванне",
"setPasswordTitle": "Задаць пароль",
"changePasswordTitle": "Змяніць пароль",
"resetPasswordTitle": "Скінуць пароль",
"encryptionKeys": "Ключы шыфравання",
"continueLabel": "Працягнуць",
"insecureDevice": "Небяспечная прылада",
"howItWorks": "Як гэта працуе",
"logInLabel": "Увайсці",
"logout": "Выйсці",
"yesLogout": "Так, выйсці",
"exit": "Выхад",
"theme": "Тема",
"lightTheme": "Светлая",
"darkTheme": "Цёмная",
"systemTheme": "Сістэманая",
"invalidKey": "Памылковы ключ",
"tryAgain": "Паспрабуйце яшчэ раз",
"confirm": "Пацвердзіць",
"emailYourLogs": "Адправіць журналы",
"exportLogs": "Экспартаваць журналы",
"about": "Аб праграме",
"privacy": "Прыватнасць",
"terms": "Умовы",
"checkStatus": "Праверыць статус",
"downloadUpdate": "Спампаваць",
"update": "Абнавіць",
"checking": "Праверка...",
"warning": "Папярэджанне",
"iUnderStand": "Ясна",
"@iUnderStand": {
"description": "Text for the button to confirm the user understands the warning"
},
"importSuccessTitle": "Ура!",
"sorry": "Прабачце",
"pendingSyncs": "Папярэджанне",
"resendEmail": "Адправіць ліст яшчэ раз",
"manualSort": "Карыстальніцкая",
"editOrder": "Рэдагаваць заказ",
"mostFrequentlyUsed": "Часта выкарыстоўваюцца",
"mostRecentlyUsed": "Нядаўна выкарыстаныя",
"activeSessions": "Актыўныя сеансы",
"terminate": "Перарваць",
"thisDevice": "Гэта прылада",
"incorrectCode": "Няправільны код",
"enterPassword": "Увядзіце пароль",
"encrypted": "Зашыфравана",
"plainText": "Звычайны тэкст",
"export": "Экспартаваць",
"singIn": "Увайсці",
"compactMode": "Кампактны рэжым",
"shouldHideCode": "Схаваць коды",
"androidBiometricHint": "Праверыць ідэнтыфікацыю",
"@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
},
"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."
},
"doNotSignOut": "Не выходзіць",
"passkey": "Ключ доступу",
"loginSessionExpired": "Сеанс завяршыўся",
"pinText": "Замацаваць",
"unpinText": "Адмацаваць",
"pinned": "Замацавана",
"tags": "Тэгі",
"createNewTag": "Стварыць новы тэг",
"tag": "Тэг",
"create": "Стварыць",
"editTag": "Рэдагаванне тэг",
"deleteTagTitle": "Выдаліць тэг?",
"viewRawCodes": "Паглядзець неапрацаваныя коды",
"rawCodes": "Неапрацаваныя коды",
"rawCodeData": "Неапрацаваныя даныя кода",
"appLock": "Блакіроўка праграмы",
"autoLock": "Аўтаблакіроўка",
"immediately": "Адразу",
"reEnterPin": "Увядзіце PIN-код яшчэ раз",
"next": "Далей",
"tapToUnlock": "Націсніце для разблакіроўкі",
"deviceLock": "Блакіроўка прылады",
"hideContent": "Схаваць змест",
"pinLock": "Блакіроўка PIN'ам",
"enterPin": "Увядзіце PIN-код",
"setNewPin": "Задаць новы PIN",
"deselectAll": "Зняць выбар з усіх",
"selectAll": "Выбраць усе",
"plainHTML": "Звычайны HTML",
"advanced": "Пашыраныя",
"algorithm": "Алгарытм",
"type": "Тып",
"period": "Перыяд",
"digits": "Лічбы"
"search": "Пошук"
}

View File

@@ -1 +0,0 @@
{}

View File

@@ -173,7 +173,6 @@
"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",
@@ -369,19 +368,16 @@
"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."
@@ -418,18 +414,6 @@
"@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."
@@ -437,7 +421,6 @@
"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)",
@@ -467,7 +450,6 @@
"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,7 +173,6 @@
"invalidQRCode": "QR 碼無效",
"noRecoveryKeyTitle": "沒有復原密鑰嗎?",
"enterEmailHint": "請輸入您的電子郵件地址",
"enterNewEmailHint": "輸入你的新電子郵件地址",
"invalidEmailTitle": "無效的電子郵件地址",
"invalidEmailMessage": "請輸入一個有效的電子郵件地址。",
"deleteAccount": "刪除帳戶",

View File

@@ -14,6 +14,7 @@ import 'package:ente_auth/services/billing_service.dart';
import 'package:ente_auth/services/notification_service.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/services/user_remote_flag_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/services/window_listener_service.dart';
import 'package:ente_auth/store/code_display_store.dart';
@@ -86,6 +87,19 @@ void main() async {
}
}
// Future<void> whiteListLetsEncryptRootCA() async {
// try {
// // https://stackoverflow.com/a/71090239
// // https://github.com/ente-io/ente/issues/2178
// ByteData data =
// await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
// SecurityContext.defaultContext
// .setTrustedCertificatesBytes(data.buffer.asUint8List());
// } catch (e) {
// _logger.severe("Failed to whitelist Let's Encrypt Root CA", e);
// }
// }
Future<void> _runInForeground() async {
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
return await _runWithLogs(() async {
@@ -102,7 +116,7 @@ Future<void> _runInForeground() async {
AppLock(
builder: (args) => App(locale: locale),
lockScreen: const LockScreen(),
enabled: await LockScreenSettings.instance.shouldShowLockScreen(),
enabled: await Configuration.instance.shouldShowLockScreen(),
locale: locale,
lightTheme: lightThemeData,
darkTheme: darkThemeData,
@@ -155,6 +169,7 @@ Future<void> _init(bool bool, {String? via}) async {
await Configuration.instance.init();
await Network.instance.init();
await UserService.instance.init();
await UserRemoteFlagService.instance.init();
await AuthenticatorService.instance.init();
await BillingService.instance.init();
await NotificationService.instance.init();

View File

@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_password.dart';
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_pin.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
@@ -41,7 +42,7 @@ class LocalAuthenticationService {
isAuthenticatingForInAppChange: true,
);
AppLock.of(context)!.setEnabled(
await LockScreenSettings.instance.shouldShowLockScreen(),
await Configuration.instance.shouldShowLockScreen(),
);
if (!result) {
showToast(context, infoMessage);
@@ -113,13 +114,12 @@ class LocalAuthenticationService {
);
if (result) {
AppLock.of(context)!.setEnabled(shouldEnableLockScreen);
await LockScreenSettings.instance
await Configuration.instance
.setSystemLockScreen(shouldEnableLockScreen);
return true;
} else {
AppLock.of(context)!.setEnabled(
await LockScreenSettings.instance.shouldShowLockScreen(),
);
AppLock.of(context)!
.setEnabled(await Configuration.instance.shouldShowLockScreen());
}
} else {
// ignore: unawaited_futures

View File

@@ -0,0 +1,141 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/events/notification_event.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
class UserRemoteFlagService {
final _dio = Network.instance.getDio();
final _logger = Logger((UserRemoteFlagService).toString());
final _config = Configuration.instance;
late SharedPreferences _prefs;
UserRemoteFlagService._privateConstructor();
static final UserRemoteFlagService instance =
UserRemoteFlagService._privateConstructor();
static const String recoveryVerificationFlag = "recoveryKeyVerified";
static const String needRecoveryKeyVerification =
"needRecoveryKeyVerification";
Future<void> init() async {
_prefs = await SharedPreferences.getInstance();
}
bool shouldShowRecoveryVerification() {
if (!_prefs.containsKey(needRecoveryKeyVerification)) {
// fetch the status from remote
unawaited(_refreshRecoveryVerificationFlag());
return false;
} else {
final bool shouldShow = _prefs.getBool(needRecoveryKeyVerification)!;
if (shouldShow) {
// refresh the status to check if user marked it as done on another device
unawaited(_refreshRecoveryVerificationFlag());
}
return shouldShow;
}
}
// markRecoveryVerificationAsDone is used to track if user has verified their
// recovery key in the past or not. This helps in avoid showing the same
// prompt to the user on re-install or signing into a different device
Future<void> markRecoveryVerificationAsDone() async {
await _updateKeyValue(recoveryVerificationFlag, true.toString());
await _prefs.setBool(needRecoveryKeyVerification, false);
}
Future<void> _refreshRecoveryVerificationFlag() async {
_logger.finest('refresh recovery key verification flag');
final remoteStatusValue =
await _getValue(recoveryVerificationFlag, "false");
final bool isNeedVerificationFlagSet =
_prefs.containsKey(needRecoveryKeyVerification);
if (remoteStatusValue.toLowerCase() == "true") {
await _prefs.setBool(needRecoveryKeyVerification, false);
// If the user verified on different device, then we should refresh
// the UI to dismiss the Notification.
if (isNeedVerificationFlagSet) {
Bus.instance.fire(NotificationEvent());
}
} else if (!isNeedVerificationFlagSet) {
// Verification is not done yet as remoteStatus is false and local flag to
// show notification isn't set. Set the flag to true if any active
// session is older than 1 day.
final activeSessions = await UserService.instance.getActiveSessions();
final int microSecondsInADay = const Duration(days: 1).inMicroseconds;
final bool anyActiveSessionOlderThanADay =
activeSessions.sessions.firstWhereOrNull(
(e) =>
(e.creationTime + microSecondsInADay) <
DateTime.now().microsecondsSinceEpoch,
) !=
null;
if (anyActiveSessionOlderThanADay) {
await _prefs.setBool(needRecoveryKeyVerification, true);
Bus.instance.fire(NotificationEvent());
} else {
// continue defaulting to no verification prompt
_logger.finest('No active session older than 1 day');
}
}
}
Future<String> _getValue(String key, String? defaultValue) async {
try {
final Map<String, dynamic> queryParams = {"key": key};
if (defaultValue != null) {
queryParams["defaultValue"] = defaultValue;
}
final response = await _dio.get(
"${_config.getHttpEndpoint()}/remote-store",
queryParameters: queryParams,
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected status code ${response.statusCode}");
}
return response.data["value"];
} catch (e) {
_logger.info("Error while fetching bool status for $key", e);
rethrow;
}
}
// _setBooleanFlag sets the corresponding flag on remote
// to mark recovery as completed
Future<void> _updateKeyValue(String key, String value) async {
try {
final response = await _dio.post(
"${_config.getHttpEndpoint()}/remote-store/update",
data: {
"key": key,
"value": value,
},
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
},
),
);
if (response.statusCode != HttpStatus.ok) {
throw Exception("Unexpected state");
}
} catch (e) {
_logger.warning("Failed to set flag for $key", e);
rethrow;
}
}
}

View File

@@ -0,0 +1,219 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_remote_flag_service.dart';
import 'package:ente_auth/ui/account/recovery_key_page.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class VerifyRecoveryPage extends StatefulWidget {
const VerifyRecoveryPage({super.key});
@override
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
}
class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
final _recoveryKey = TextEditingController();
final Logger _logger = Logger((_VerifyRecoveryPageState).toString());
void _verifyRecoveryKey() async {
final dialog =
createProgressDialog(context, context.l10n.verifyingRecoveryKey);
await dialog.show();
try {
final String inputKey = _recoveryKey.text.trim();
final String recoveryKey =
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
try {
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
} catch (e) {
await dialog.hide();
if (e is DioException && e.type == DioExceptionType.unknown) {
await showErrorDialog(
context,
"No internet connection",
"Please check your internet connection and try again.",
);
} else {
await showGenericErrorDialog(
context: context,
error: e,
);
}
return;
}
await dialog.hide();
// todo: change this as per figma once the component is ready
await showErrorDialog(
context,
context.l10n.recoveryKeyVerified,
context.l10n.recoveryKeySuccessBody,
);
Navigator.of(context).pop();
} else {
throw Exception("recovery key didn't match");
}
} catch (e, s) {
_logger.severe("failed to verify recovery key", e, s);
await dialog.hide();
final String errMessage = context.l10n.invalidRecoveryKey;
final result = await showChoiceDialog(
context,
title: context.l10n.invalidKey,
body: errMessage,
firstButtonLabel: context.l10n.tryAgain,
secondButtonLabel: context.l10n.viewRecoveryKey,
secondButtonAction: ButtonAction.second,
);
if (result!.action == ButtonAction.second) {
await _onViewRecoveryKeyClick();
}
}
}
Future<void> _onViewRecoveryKeyClick() async {
final hasAuthenticated =
await LocalAuthenticationService.instance.requestLocalAuthentication(
context,
"Please authenticate to view your recovery key",
);
await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
String recoveryKey;
try {
recoveryKey =
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
await routeToPage(
context,
RecoveryKeyPage(
recoveryKey,
context.l10n.ok,
showAppBar: true,
onDone: () {
Navigator.of(context).pop();
},
),
);
} catch (e) {
// ignore: unawaited_futures
showGenericErrorDialog(
context: context,
error: e,
);
return;
}
}
}
@override
Widget build(BuildContext context) {
final enteTheme = Theme.of(context).colorScheme.enteTheme;
return Scaffold(
appBar: AppBar(
elevation: 0,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
color: Theme.of(context).iconTheme.color,
onPressed: () {
Navigator.of(context).pop();
},
),
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minWidth: constraints.maxWidth,
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Column(
children: [
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: Text(
context.l10n.confirmRecoveryKey,
style: enteTheme.textTheme.h3Bold,
textAlign: TextAlign.left,
),
),
const SizedBox(height: 18),
Text(
context.l10n.recoveryKeyVerifyReason,
style: enteTheme.textTheme.small
.copyWith(color: enteTheme.colorScheme.textMuted),
),
const SizedBox(height: 12),
TextFormField(
decoration: InputDecoration(
filled: true,
hintText: context.l10n.enterYourRecoveryKey,
contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
style: const TextStyle(
fontSize: 14,
fontFeatures: [FontFeature.tabularFigures()],
),
controller: _recoveryKey,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.multiline,
minLines: 4,
maxLines: null,
onChanged: (_) {
setState(() {});
},
),
const SizedBox(height: 12),
Expanded(
child: Container(
alignment: Alignment.bottomCenter,
width: double.infinity,
padding: const EdgeInsets.fromLTRB(0, 12, 0, 40),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
GradientButton(
onTap: _verifyRecoveryKey,
text: context.l10n.confirm,
),
const SizedBox(height: 8),
],
),
),
),
const SizedBox(height: 20),
],
),
),
),
);
},
),
),
);
}
}

View File

@@ -319,7 +319,7 @@ class _HomePageState extends State<HomePage> {
Future<void> navigateToLockScreen() async {
final bool shouldShowLockScreen =
await LockScreenSettings.instance.shouldShowLockScreen();
await Configuration.instance.shouldShowLockScreen();
if (shouldShowLockScreen) {
await AppLock.of(context)!.showLockScreen();
} else {

View File

@@ -1,6 +1,7 @@
import "dart:async";
import "dart:io";
import "package:ente_auth/core/configuration.dart";
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/services/local_authentication_service.dart";
import "package:ente_auth/theme/ente_theme.dart";
@@ -30,7 +31,8 @@ class LockScreenOptions extends StatefulWidget {
}
class _LockScreenOptionsState extends State<LockScreenOptions> {
final LockScreenSettings _lockScreenSettings = LockScreenSettings.instance;
final Configuration _configuration = Configuration.instance;
final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
late bool appLock = false;
bool isPinEnabled = false;
bool isPasswordEnabled = false;
@@ -41,18 +43,18 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
@override
void initState() {
super.initState();
hideAppContent = _lockScreenSettings.getShouldHideAppContent();
autoLockTimeInMilliseconds = _lockScreenSettings.getAutoLockTime();
hideAppContent = _lockscreenSetting.getShouldHideAppContent();
autoLockTimeInMilliseconds = _lockscreenSetting.getAutoLockTime();
_initializeSettings();
appLock = _lockScreenSettings.getIsAppLockSet();
appLock = _lockscreenSetting.getIsAppLockSet();
}
Future<void> _initializeSettings() async {
final bool passwordEnabled = await _lockScreenSettings.isPasswordSet();
final bool pinEnabled = await _lockScreenSettings.isPinSet();
final bool passwordEnabled = await _lockscreenSetting.isPasswordSet();
final bool pinEnabled = await _lockscreenSetting.isPinSet();
final bool shouldHideAppContent =
_lockScreenSettings.getShouldHideAppContent();
final bool systemLockEnabled = _lockScreenSettings.shouldShowSystemLockScreen();
_lockscreenSetting.getShouldHideAppContent();
final bool systemLockEnabled = _configuration.shouldShowSystemLockScreen();
setState(() {
isPasswordEnabled = passwordEnabled;
isPinEnabled = pinEnabled;
@@ -64,8 +66,8 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
Future<void> _deviceLock() async {
if (await LocalAuthenticationService.instance
.isLocalAuthSupportedOnDevice()) {
await _lockScreenSettings.removePinAndPassword();
await _lockScreenSettings.setSystemLockScreen(!isSystemLockEnabled);
await _lockscreenSetting.removePinAndPassword();
await _configuration.setSystemLockScreen(!isSystemLockEnabled);
} else {
await showDialogWidget(
context: context,
@@ -94,10 +96,10 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
);
if (result) {
await _lockScreenSettings.setSystemLockScreen(false);
await _lockScreenSettings.setAppLockEnabled(true);
await _configuration.setSystemLockScreen(false);
await _lockscreenSetting.setAppLockEnabled(true);
setState(() {
appLock = _lockScreenSettings.getIsAppLockSet();
appLock = _lockscreenSetting.getIsAppLockSet();
});
}
await _initializeSettings();
@@ -112,9 +114,9 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
),
);
if (result) {
await _lockScreenSettings.setSystemLockScreen(false);
await _configuration.setSystemLockScreen(false);
setState(() {
appLock = _lockScreenSettings.getIsAppLockSet();
appLock = _lockscreenSetting.getIsAppLockSet();
});
}
await _initializeSettings();
@@ -124,17 +126,17 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
AppLock.of(context)!.setEnabled(!appLock);
if (await LocalAuthenticationService.instance
.isLocalAuthSupportedOnDevice()) {
await _lockScreenSettings.setSystemLockScreen(!appLock);
await _lockScreenSettings.setAppLockEnabled(!appLock);
await _configuration.setSystemLockScreen(!appLock);
await _lockscreenSetting.setAppLockEnabled(!appLock);
} else {
await _lockScreenSettings.setSystemLockScreen(false);
await _lockScreenSettings.setAppLockEnabled(false);
await _configuration.setSystemLockScreen(false);
await _lockscreenSetting.setAppLockEnabled(false);
}
await _lockScreenSettings.removePinAndPassword();
await _lockscreenSetting.removePinAndPassword();
if (PlatformUtil.isMobile()) {
await _lockScreenSettings.setHideAppContent(!appLock);
await _lockscreenSetting.setHideAppContent(!appLock);
setState(() {
hideAppContent = _lockScreenSettings.getShouldHideAppContent();
hideAppContent = _lockscreenSetting.getShouldHideAppContent();
});
}
await _initializeSettings();
@@ -150,7 +152,7 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
).then(
(value) {
setState(() {
autoLockTimeInMilliseconds = _lockScreenSettings.getAutoLockTime();
autoLockTimeInMilliseconds = _lockscreenSetting.getAutoLockTime();
});
},
);
@@ -160,7 +162,7 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
setState(() {
hideAppContent = !hideAppContent;
});
await _lockScreenSettings.setHideAppContent(hideAppContent);
await _lockscreenSetting.setHideAppContent(hideAppContent);
}
String _formatTime(Duration duration) {

View File

@@ -166,7 +166,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
return;
}
}
if (await LockScreenSettings.instance.shouldShowLockScreen()) {
if (await Configuration.instance.shouldShowLockScreen()) {
final bool result = await requestAuthentication(
context,
context.l10n.authToChangeLockscreenSetting,

View File

@@ -53,7 +53,6 @@ Future<bool> requestAuthentication(
signInTitle: l10n.androidSignInTitle,
),
IOSAuthMessages(
localizedFallbackTitle: l10n.enterPassword,
goToSettingsButton: l10n.goToSettings,
goToSettingsDescription: l10n.goToSettings,
lockOut: l10n.iOSLockOut,

View File

@@ -3,8 +3,6 @@ import "dart:io";
import "dart:typed_data";
import "package:ente_auth/core/configuration.dart";
import "package:ente_auth/core/event_bus.dart";
import "package:ente_auth/events/signed_out_event.dart";
import "package:ente_auth/utils/platform_util.dart";
import "package:ente_crypto_dart/ente_crypto_dart.dart";
import "package:flutter/material.dart";
@@ -28,7 +26,6 @@ class LockScreenSettings {
static const keyHasMigratedLockScreenChanges =
"ls_has_migrated_lock_screen_changes";
static const keyShowOfflineModeWarning = "ls_show_offline_mode_warning";
static const keyShouldShowLockScreen = "should_show_lock_screen";
static const String kIsLightMode = "is_light_mode";
final List<Duration> autoLockDurations = const [
@@ -55,10 +52,6 @@ class LockScreenSettings {
await runLockScreenChangesMigration();
await _clearLsDataInKeychainIfFreshInstall();
Bus.instance.on<SignedOutEvent>().listen((event) {
removePinAndPassword();
});
}
Future<void> setOfflineModeWarningStatus(bool value) async {
@@ -76,7 +69,8 @@ class LockScreenSettings {
final bool passwordEnabled = await isPasswordSet();
final bool pinEnabled = await isPinSet();
final bool systemLockEnabled = shouldShowSystemLockScreen();
final bool systemLockEnabled =
Configuration.instance.shouldShowSystemLockScreen();
if (passwordEnabled || pinEnabled || systemLockEnabled) {
await setAppLockEnabled(true);
@@ -220,24 +214,6 @@ class LockScreenSettings {
return await _secureStorage.containsKey(key: password);
}
Future<bool> shouldShowLockScreen() async {
final bool isPin = await isPinSet();
final bool isPass = await isPasswordSet();
return isPin || isPass || shouldShowSystemLockScreen();
}
bool shouldShowSystemLockScreen() {
if (_preferences.containsKey(keyShouldShowLockScreen)) {
return _preferences.getBool(keyShouldShowLockScreen)!;
} else {
return false;
}
}
Future<void> setSystemLockScreen(bool value) {
return _preferences.setBool(keyShouldShowLockScreen, value);
}
// If the app was uninstalled (without logging out if it was used with
// backups), keychain items of the app persist in the keychain. To avoid using
// old keychain items, we delete them on reinstall.

View File

@@ -543,74 +543,67 @@ packages:
flutter_inappwebview:
dependency: "direct main"
description:
path: flutter_inappwebview
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "6.2.0-beta.3"
name: flutter_inappwebview
sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
flutter_inappwebview_android:
dependency: transitive
description:
path: flutter_inappwebview_android
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
name: flutter_inappwebview_android
sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
flutter_inappwebview_internal_annotations:
dependency: transitive
description:
name: flutter_inappwebview_internal_annotations
sha256: "787171d43f8af67864740b6f04166c13190aa74a1468a1f1f1e9ee5b90c359cd"
sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
version: "1.1.1"
flutter_inappwebview_ios:
dependency: transitive
description:
path: flutter_inappwebview_ios
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
name: flutter_inappwebview_ios
sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_macos:
dependency: transitive
description:
path: flutter_inappwebview_macos
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
name: flutter_inappwebview_macos
sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_platform_interface:
dependency: transitive
description:
path: flutter_inappwebview_platform_interface
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.4.0-beta.3"
name: flutter_inappwebview_platform_interface
sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500
url: "https://pub.dev"
source: hosted
version: "1.3.0+1"
flutter_inappwebview_web:
dependency: transitive
description:
path: flutter_inappwebview_web
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "1.2.0-beta.3"
name: flutter_inappwebview_web
sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
flutter_inappwebview_windows:
dependency: transitive
description:
path: flutter_inappwebview_windows
ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
resolved-ref: "3e6c4c4a25340cd363af9d38891d88498b90be26"
url: "https://github.com/pichillilorenzo/flutter_inappwebview.git"
source: git
version: "0.7.0-beta.3"
name: flutter_inappwebview_windows
sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
flutter_launcher_icons:
dependency: "direct main"
description:

View File

@@ -1,7 +1,7 @@
name: ente_auth
description: ente two-factor authenticator
version: 4.4.1+441
version: 4.4.0+440
publish_to: none
environment:
@@ -45,13 +45,7 @@ dependencies:
flutter_context_menu: ^0.2.0
flutter_displaymode: ^0.6.0
flutter_email_sender: ^6.0.2
# revert to pub.dev when merged
# https://github.com/pichillilorenzo/flutter_inappwebview/pull/2548
flutter_inappwebview:
git:
url: https://github.com/pichillilorenzo/flutter_inappwebview.git
path: flutter_inappwebview
ref: 3e6c4c4a25340cd363af9d38891d88498b90be26
flutter_inappwebview: ^6.1.5
flutter_launcher_icons: ^0.14.1
flutter_local_authentication:
git:

View File

@@ -5,6 +5,8 @@
endpoint:
api: "http://localhost:8080"
# Endpoint for the account service for passkey
accounts: "http://localhost:3001"
log:
http: false # log status code & time taken by requests

View File

@@ -35,7 +35,6 @@ type AuthorizationResponse struct {
ID int64 `json:"id"`
KeyAttributes *KeyAttributes `json:"keyAttributes,omitempty"`
EncryptedToken string `json:"encryptedToken,omitempty"`
AccountsUrl string `json:"accountsUrl"`
Token string `json:"token,omitempty"`
TwoFactorSessionID string `json:"twoFactorSessionID"`
PassKeySessionID string `json:"passkeySessionID"`

View File

@@ -110,6 +110,7 @@ func initConfig(cliConfigDir string) {
viper.AddConfigPath(".") // optionally look for config in the working directory
viper.SetDefault("endpoint.api", constants.EnteApiUrl)
viper.SetDefault("endpoint.accounts", constants.EnteAccountUrl)
viper.SetDefault("log.http", false)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {

View File

@@ -8,8 +8,8 @@ import (
eCrypto "github.com/ente-io/cli/internal/crypto"
"github.com/ente-io/cli/pkg/model"
"github.com/ente-io/cli/utils/browser"
"github.com/ente-io/cli/utils/constants"
"github.com/ente-io/cli/utils/encoding"
"github.com/spf13/viper"
"log"
"github.com/kong/go-srp"
@@ -145,10 +145,7 @@ func (c *ClICtrl) verifyPassKey(ctx context.Context, authResp *api.Authorization
if !authResp.IsPasskeyRequired() {
return authResp, nil
}
baseAccountUrl := constants.EnteAccountUrl
if authResp.AccountsUrl != "" {
baseAccountUrl = authResp.AccountsUrl
}
baseAccountUrl := viper.GetString("endpoint.accounts")
passkeyAuthUrl := fmt.Sprintf("%s/passkeys/verify?passkeySessionID=%s&redirect=ente-cli://passkey&clientPackage=%s", baseAccountUrl, authResp.PassKeySessionID, app.ClientPkg())
fmt.Printf("Open this url in browser to verify passkey: %s\n", passkeyAuthUrl)
err := browser.OpenURL(passkeyAuthUrl)

View File

@@ -113,11 +113,6 @@ jobs:
APPLE_APP_SPECIFIC_PASSWORD:
${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
# Windows Azure Trusted Signing related values
# https://www.electron.build/code-signing-win#using-azure-trusted-signing-beta
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
# Default is "draft", but since our nightly builds update
# existing pre-releases, set this to "prerelease".
EP_PRE_RELEASE: true

View File

@@ -1,13 +1,9 @@
# CHANGELOG
## v1.7.15 (Unreleased)
## v1.7.14 (Unreleased)
- .
## v1.7.14
- Increase file size limit to 10 GB.
## v1.7.13
- Generate streams for videos (beta)

View File

@@ -3,7 +3,6 @@
- [Electron](#electron)
- [Dev dependencies](#dev)
- [Functionality](#functionality)
- [Pinned](#pinned)
## Electron
@@ -141,24 +140,3 @@ handles to avoid reopening them for every operation.
[chokidar](https://github.com/paulmillr/chokidar) is used as a file system
watcher for the watch folders functionality.
## Pinned
- `electron-builder` is pinned to 26.0.16 because of
https://github.com/electron-userland/electron-builder/issues/9161#issuecomment-2977829326
- `electron-builder` is pinned to 26.0.14 because of a new error when building:
> Detected file
> "Contents/Resources/app.asar.unpacked/node_modules/onnxruntime-node/bin/napi-v3/darwin/arm64/libonnxruntime.1.20.1.dylib"
> that's the same in both x64 and arm64 builds and not covered by the
> x64ArchFiles rule: "undefined" failedTask=build stackTrace=Error: Detected
> file
> "Contents/Resources/app.asar.unpacked/node_modules/onnxruntime-node/bin/napi-v3/darwin/arm64/libonnxruntime.1.20.1.dylib"
> that's the same in both x64 and arm64 builds and not covered by the
> x64ArchFiles rule: "undefined"
To reproduce this locally, add `x64ArchFiles: "ffmpeg"` to
`electron-builder.yml`, then run `node_modules/.bin/electron-builder --mac`
- `electron-store` is pinned to 8.2.0 because subsequent versions are ESM only.

View File

@@ -14,11 +14,6 @@ win:
target:
- target: nsis
arch: [x64, arm64]
azureSignOptions:
publisherName: ENTE TECHNOLOGIES, INC.
endpoint: https://eus.codesigning.azure.net/
certificateProfileName: EnteTrustCertProfile
codeSigningAccountName: EnteTechnologiesInc
nsis:
deleteAppDataOnUninstall: true
linux:

View File

@@ -1,6 +1,6 @@
{
"name": "ente",
"version": "1.7.15-beta",
"version": "1.7.14-beta",
"private": true,
"description": "Desktop client for Ente Photos",
"repository": "github:ente-io/photos-desktop",
@@ -31,32 +31,33 @@
"clip-bpe-js": "^0.0.6",
"comlink": "^4.4.2",
"compare-versions": "^6.1.1",
"electron-log": "^5.4.1",
"electron-log": "^5.4.0",
"electron-store": "^8.2.0",
"electron-updater": "^6.6.5",
"electron-updater": "^6.6.3",
"ffmpeg-static": "^5.2.0",
"lru-cache": "^11.1.0",
"next-electron-server": "^1.0.0",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.20.1",
"zod": "^3.25.67"
"zod": "^3.25.51"
},
"devDependencies": {
"@eslint/js": "^9.29.0",
"@eslint/js": "^9.28.0",
"@tsconfig/node22": "^22.0.2",
"@types/auto-launch": "^5.0.5",
"@types/ffmpeg-static": "^3.0.3",
"ajv": "^8.17.1",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"electron": "^37.1.0",
"electron-builder": "26.0.14",
"electron": "^36.4.0",
"electron-builder": "^26.0.14",
"eslint": "^9",
"prettier": "3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"prettier-plugin-packagejson": "^2.5.15",
"shx": "^0.4.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.34.1"
"typescript-eslint": "^8.33.1"
},
"packageManager": "yarn@1.22.22",
"productName": "ente"

View File

@@ -78,6 +78,14 @@ export const allowWindowClose = (): void => {
* We call this at the end of this file.
*/
const main = () => {
// Workaround for Electron 36 not launching on some Linux distros. Remove
// once fixed or otherwise mitigated upstream.
//
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
if (process.platform == "linux") {
app.commandLine.appendSwitch("gtk-version", "3");
}
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();

View File

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

View File

@@ -184,13 +184,14 @@ const downloadModel = async (saveLocation: string, name: string) => {
/**
* Create an ONNX {@link InferenceSession} with some defaults.
*/
const createInferenceSession = (modelPath: string) =>
ort.InferenceSession.create(modelPath, {
const createInferenceSession = async (modelPath: string) => {
return await ort.InferenceSession.create(modelPath, {
// Restrict the number of threads to 1.
intraOpNumThreads: 1,
// Be more conservative with RAM usage.
enableCpuMemArena: false,
});
};
const cachedCLIPImageSession = makeCachedInferenceSession(
"mobileclip_s2_image_opset18_rgba_opt.onnx",
@@ -232,11 +233,9 @@ const getTokenizer = () => (_tokenizer ??= new Tokenizer());
export const computeCLIPTextEmbeddingIfAvailable = async (text: string) => {
const sessionOrSkip = await Promise.race([
cachedCLIPTextSession(),
// Wait a bit to get the session promise to resolved the first time this
// code runs on each app start (in these cases the model will already be
// downloaded, so session creation should take only a 1 or 2 ticks: file
// system stat, and ort.InferenceSession.create).
wait(50).then(() => 1),
// Wait for a tick to get the session promise to resolved the first time
// this code runs on each app start (and the model has been downloaded).
wait(0).then(() => 1),
]);
// Don't wait for the download to complete.

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 saveMasterKeyInSafeStorage = (masterKey: string) => {
const encryptedKeyBuffer = safeStorage.encryptString(masterKey);
const encryptedKey = Buffer.from(encryptedKeyBuffer).toString("base64");
safeStorageStore.set("encryptionKey", encryptedKey);
export const saveMasterKeyB64 = (masterKeyB64: string) => {
const encryptedKey = safeStorage.encryptString(masterKeyB64);
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
safeStorageStore.set("encryptionKey", b64EncryptedKey);
};
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 masterKeyB64 = (): string | undefined => {
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
if (!b64EncryptedKey) return undefined;
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
return safeStorage.decryptString(keyBuffer);
};
export const lastShownChangelogVersion = (): number | undefined =>

View File

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

View File

@@ -25,7 +25,7 @@
ajv "^6.12.0"
ajv-keywords "^3.4.1"
"@electron/asar@3.4.1", "@electron/asar@^3.2.7":
"@electron/asar@3.4.1":
version "3.4.1"
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065"
integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==
@@ -34,6 +34,15 @@
glob "^7.1.6"
minimatch "^3.0.4"
"@electron/asar@^3.2.7":
version "3.2.18"
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.18.tgz#fa607f829209bab8b9e0ce6658d3fe81b2cba517"
integrity sha512-2XyvMe3N3Nrs8cV39IKELRHTYUWFKrmqqSY1U+GMlc0jvqjIVnoxhNd2H4JolWQncbJi1DCvb5TNxZuI2fEjWg==
dependencies:
commander "^5.0.0"
glob "^7.1.6"
minimatch "^3.0.4"
"@electron/fuses@^1.8.0":
version "1.8.0"
resolved "https://registry.yarnpkg.com/@electron/fuses/-/fuses-1.8.0.tgz#ad34d3cc4703b1258b83f6989917052cfc1490a0"
@@ -175,10 +184,10 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.9.1.tgz#4a97e85e982099d6c7ee8410aacb55adaa576f06"
integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==
"@eslint/js@^9.29.0":
version "9.29.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.29.0.tgz#dc6fd117c19825f8430867a662531da36320fe56"
integrity sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==
"@eslint/js@^9.28.0":
version "9.28.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.28.0.tgz#7822ccc2f8cae7c3cd4f902377d520e9ae03f844"
integrity sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==
"@eslint/object-schema@^2.1.4":
version "2.1.4"
@@ -200,18 +209,6 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570"
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
"@isaacs/balanced-match@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29"
integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==
"@isaacs/brace-expansion@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3"
integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==
dependencies:
"@isaacs/balanced-match" "^4.0.1"
"@isaacs/fs-minipass@^4.0.0":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
@@ -327,6 +324,11 @@
dependencies:
"@types/ms" "*"
"@types/ffmpeg-static@^3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/ffmpeg-static/-/ffmpeg-static-3.0.3.tgz#605358ac6304507a75c2fd5fd861534837b19e2f"
integrity sha512-wmjANN0CiYs5clQESK+xE6plet0y9ndqaNBdQx4IIw7ZbPBMQw+14Lq4ky2WqMqGlpFJ9ZUxU0O43TvVZziyyA==
"@types/fs-extra@9.0.13", "@types/fs-extra@^9.0.11":
version "9.0.13"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-9.0.13.tgz#7594fbae04fe7f1918ce8b3d213f74ff44ac1f45"
@@ -390,78 +392,78 @@
dependencies:
"@types/node" "*"
"@typescript-eslint/eslint-plugin@8.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz#56cf35b89383eaf2bdcf602f5bbdac6dbb11e51b"
integrity sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==
"@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.34.1"
"@typescript-eslint/type-utils" "8.34.1"
"@typescript-eslint/utils" "8.34.1"
"@typescript-eslint/visitor-keys" "8.34.1"
"@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.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.34.1.tgz#f102357ab3a02d5b8aa789655905662cc5093067"
integrity sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==
"@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.34.1"
"@typescript-eslint/types" "8.34.1"
"@typescript-eslint/typescript-estree" "8.34.1"
"@typescript-eslint/visitor-keys" "8.34.1"
"@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.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.34.1.tgz#20501f8b87202c45f5e70a5b24dcdcb8fe12d460"
integrity sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==
"@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.34.1"
"@typescript-eslint/types" "^8.34.1"
"@typescript-eslint/tsconfig-utils" "^8.33.1"
"@typescript-eslint/types" "^8.33.1"
debug "^4.3.4"
"@typescript-eslint/scope-manager@8.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz#727ea43441f4d23d5c73d34195427d85042e5117"
integrity sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==
"@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.34.1"
"@typescript-eslint/visitor-keys" "8.34.1"
"@typescript-eslint/types" "8.33.1"
"@typescript-eslint/visitor-keys" "8.33.1"
"@typescript-eslint/tsconfig-utils@8.34.1", "@typescript-eslint/tsconfig-utils@^8.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz#d6abb1b1e9f1f1c83ac92051c8fbf2dbc4dc9f5e"
integrity sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==
"@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.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.34.1.tgz#df860d8edefbfe142473ea4defb7408edb0c379e"
integrity sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==
"@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.34.1"
"@typescript-eslint/utils" "8.34.1"
"@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.34.1", "@typescript-eslint/types@^8.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.34.1.tgz#565a46a251580dae674dac5aafa8eb14b8322a35"
integrity sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==
"@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.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz#befdb042a6bc44fdad27429b2d3b679c80daad71"
integrity sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==
"@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.34.1"
"@typescript-eslint/tsconfig-utils" "8.34.1"
"@typescript-eslint/types" "8.34.1"
"@typescript-eslint/visitor-keys" "8.34.1"
"@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"
@@ -469,23 +471,23 @@
semver "^7.6.0"
ts-api-utils "^2.1.0"
"@typescript-eslint/utils@8.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.34.1.tgz#f98c9b0c5cae407e34f5131cac0f3a74347a398e"
integrity sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==
"@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.34.1"
"@typescript-eslint/types" "8.34.1"
"@typescript-eslint/typescript-estree" "8.34.1"
"@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.34.1":
version "8.34.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz#28a1987ea3542ccafb92aa792726a304b39531cf"
integrity sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==
"@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.34.1"
eslint-visitor-keys "^4.2.1"
"@typescript-eslint/types" "8.33.1"
eslint-visitor-keys "^4.2.0"
"@xmldom/xmldom@^0.8.8":
version "0.8.10"
@@ -1191,7 +1193,7 @@ ejs@^3.1.8:
dependencies:
jake "^10.8.5"
electron-builder@26.0.14:
electron-builder@^26.0.14:
version "26.0.14"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-26.0.14.tgz#8927c6da42a69425d15e08f351e944ea0e7866da"
integrity sha512-YBxpWLMGj0oS7fbS3LVingeZqFunU0F8s+uB9QTd5+wN4qgrf/rSGRkqoImbWg2+F2yHq11wmaA/Xr9xzvgQ0w==
@@ -1207,10 +1209,10 @@ electron-builder@26.0.14:
simple-update-notifier "2.0.0"
yargs "^17.6.2"
electron-log@^5.4.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-5.4.1.tgz#700ddc6ef4b06c13a983468580ba7a7e579129d4"
integrity sha512-QvisA18Z++8E3Th0zmhUelys9dEv7aIeXJlbFw3UrxCc8H9qSRW0j8/ooTef/EtHui8tVmbKSL+EIQzP9GoRLg==
electron-log@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-5.4.0.tgz#3180bf5194b2e2efacb62ec1392f8150faf4de6b"
integrity sha512-AXI5OVppskrWxEAmCxuv8ovX+s2Br39CpCAgkGMNHQtjYT3IiVbSQTncEjFVGPgoH35ZygRm/mvUMBDWwhRxgg==
electron-publish@26.0.13:
version "26.0.13"
@@ -1234,10 +1236,10 @@ electron-store@^8.2.0:
conf "^10.2.0"
type-fest "^2.17.0"
electron-updater@^6.6.5:
version "6.6.5"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.5.tgz#6614daa2f737c294471eee7ce7b61deda0d5543a"
integrity sha512-jnk38WfByl2Pb0cje02xls/pJkvkq3AQZI7usDCLriU23adkerLTkRrugbCPuUxUOa79nY1g/rokHPWHZFBKyA==
electron-updater@^6.6.3:
version "6.6.3"
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.3.tgz#a1f53671ffbb08a475d495d48f0c0d971e665d5d"
integrity sha512-i448/SwMtqxy5wqAcXScnWjiFxZp+hmWA2jZCmojcdfodEGhi/DWTdRP01mE3lCILb8hmdE28SBaHf1oQW3+kw==
dependencies:
builder-util-runtime "9.3.2"
fs-extra "^10.1.0"
@@ -1248,10 +1250,10 @@ electron-updater@^6.6.5:
semver "^7.6.3"
tiny-typed-emitter "^2.1.0"
electron@^37.1.0:
version "37.1.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-37.1.0.tgz#6d6d1891f8add5d2d44007e2ee5d4542140fc4b4"
integrity sha512-Fcr3yfAw4oU392waVZSlrFUQx4P+h/k31+PRgkBY9tFx9E/zxzdPQQj0achZlG1HRDusw3ooQB+OXb9PvufdzA==
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"
@@ -1326,16 +1328,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^4.0.0:
eslint-visitor-keys@^4.0.0, eslint-visitor-keys@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45"
integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
eslint-visitor-keys@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@^9:
version "9.9.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.9.1.tgz#147ac9305d56696fb84cf5bdecafd6517ddc77ec"
@@ -2279,11 +2276,11 @@ mimic-response@^3.1.0:
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
minimatch@^10.0.0:
version "10.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa"
integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==
version "10.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
dependencies:
"@isaacs/brace-expansion" "^5.0.0"
brace-expansion "^2.0.1"
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
@@ -3254,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.34.1:
version "8.34.1"
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.34.1.tgz#4bab64b298531b9f6f3ff59b41a7161321ef8cd6"
integrity sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==
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.34.1"
"@typescript-eslint/parser" "8.34.1"
"@typescript-eslint/utils" "8.34.1"
"@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"
@@ -3424,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.67:
version "3.25.67"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.67.tgz#62987e4078e2ab0f63b491ef0c4f33df24236da8"
integrity sha512-idA2YXwpCdqUSKRCACDE6ItZD9TZzy3OZMtpfLoh6oPR47lipysRrJfjzMqFxQ3uJuUPyUeWe1r9vLH33xO/Qw==
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

@@ -8,6 +8,13 @@ export default defineConfig({
head: [["link", { rel: "icon", type: "image/png", href: "/favicon.png" }]],
cleanUrls: true,
ignoreDeadLinks: "localhostLinks",
vite: {
build: {
rollupOptions: {
external: ["client-museum-s3.png"], // Added to handle static asset import
},
},
},
themeConfig: {
// We use the default theme (with some CSS color overrides). This
// themeConfig block can be used to further customize the default theme.

View File

@@ -2,6 +2,27 @@
// appropriate place here.
export const sidebar = [
{
text: "Overview",
items: [
{
text: "Introduction",
link: "/overview/",
},
{
text: "Community",
link: "/overview/community",
},
{
text: "Contributing",
link: "/overview/contribute",
},
{
text: "Help",
link: "/overview/help",
},
],
},
{
text: "Photos",
items: [
@@ -52,10 +73,6 @@ export const sidebar = [
link: "/photos/features/machine-learning",
},
{ text: "Map", link: "/photos/features/map" },
{
text: "Notifications",
link: "/photos/features/notifications",
},
{
text: "Passkeys",
link: "/photos/features/passkeys",
@@ -318,7 +335,7 @@ export const sidebar = [
items: [
{
text: "Ente via Tailscale",
link: "/self-hosting/guides/tailscale",
link: "/self-hosting/guides/Tailscale",
},
{
text: "Ente with External S3",

View File

@@ -10,4 +10,4 @@ Ende-zu-Ende-verschlüsselte Authenticator-App für jedermann. Wir sind froh, da
du hier bist!
**Please note that this German translation is currently just a placeholder.**
Know German? [Help us fill this in!](/#contribute).
Know German? [Help us fill this in!](/overview/contribute).

View File

@@ -1,82 +1,15 @@
---
title: Home
description: >
Introduction to Ente: Products, Community and Support
---
# Welcome!
![Ducky: Ente's Mascot](/public/ducky.png){width=50% style="margin: 0 auto"}
This site contains documentation and help for Ente Photos and Ente Auth. It
describes various features, and also offers various troubleshooting suggestions.
## Introduction
Use the **sidebar** menu to navigate to information about the product (Photos or
Auth) you'd like to know more about. Or use the **search** at the top to try and
jump directly to page that might contain the information you need.
Ente (pronounced en-_tay_) is a end-to-end encrypted platform for privately,
reliably, and securely storing your data on the cloud, over which 2 applications
have been developed and made available for mobile, web and desktop, namely:
- **Ente Photos** - An alternative to Google Photos and Apple Photos
- **Ente Auth** - A free 2FA alternative to Authy
## History
Ente was the founded by Vishnu Mohandas (he's also Ente's CEO) in response to
privacy concerns with major tech companies. The underlying motivation was the
understanding that big tech had no incentive to fix their act, but with
end-to-end encrypted cross platform apps, there was a way for people to take
back control over their own data without sacrificing on features.
### Origin of the name
In Malayalam, Vishnu's native language, "ente" means "mine", thus "Ente Photos"
literally means "my photos".
But one day, he discovered that "ente" means "duck" in German. This unexpected
connection sealed the deal after looking for alternative names and led to the
adoption of ["Ducky"](https://ente.io/blog/ducky/), representing the playfulness
and friendly nature of the community and team.
## Getting Started
We recommend reading the documentation for [Ente Photos](/photos/) or
[Ente Auth](/auth/) to get started with installation on the desired platform,
explore available features and usage.
If you are looking to self-host Ente, we recommend you to read the
[official documentation](/self-hosting/) for updated information on getting
started, installation, administration and maintenance.
## Contributing
There are many ways to support Ente and you don't have to be a programmer for
that. You can spread the word, give feedback, report bugs, help us with
translations, contribute documentation and community guides and more.
To suggest new features and/or offer your perspective on how we should design
(planned and upcoming features), use our
[GitHub discussions](https://github.com/ente-io/ente/discussions)
You can find our contribution guidelines
[here](https://github.com/ente-io/ente/blob/main/CONTRIBUTING.md).
You can always engage with our community and team to hang out, answer queries
and stay updated:
- Chat: [Discord](https://ente.io/discord)
- Discussions: [GitHub](https://github.com/ente-io/ente/discussions)
- Socials:
- Twitter: [enteio](https://twitter.com/enteio)
- Mastodon: [@ente@fosstodon.org](https://fosstodon.org/@ente)
- Bluesky: [ente.io](https://bsky.app/profile/ente.io)
- Instagram: [ente.app](https://www.instagram.com/ente.app)
- Website:
- [Blog](https://ente.io/blog)
- [RSS](https://ente.io/blog/rss.xml)
## Getting Help
If you encounter any issues with any of the products that's not answered by our
documentation, 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)
To know more about Ente, see [overview](/overview/) or visit our website
[ente.io](https://ente.io).

View File

@@ -0,0 +1,21 @@
---
title: Community
description: >
Information regarding Ente's community channels
---
# 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))
## Socials
You can also follow us on [Twitter](https://twitter.com/enteio) or toot to us on
[Mastodon](https://mstdn.social/@ente)

View File

@@ -0,0 +1,29 @@
---
title: Contribute
description: Details about how to contribute to Ente
---
# Contributing
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
[GitHub discussions](https://github.com/ente-io/ente/discussions)
## Documentation
To contribute to these docs, you can use the "Edit this page" button at the
bottom of each page. This will allow you to directly edit the markdown file that
is used to generate this documentation and open a quick pull request directly
from GitHub's UI.
If you're more comfortable in contributing with your text editor, see the
`docs/` folder of our GitHub repository,
[github.com/ente-io/ente](https://github.com/ente-io/ente).

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@@ -0,0 +1,13 @@
---
title: Help
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
[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

@@ -0,0 +1,50 @@
---
title: Introduction
description: >
An overview of Ente: the company, and the people behind it, and the products
that we make.
---
# About
Ente (pronounced en-_tay_. Like ca<i>fe</i>) is a end-to-end encrypted platform
for privately, reliably, and securely storing your data on the cloud. On top of
this platform, Ente offers two products:
- **Ente Photos** - An alternative to Google Photos and Apple Photos
- **Ente Auth** - A free 2FA alternative to Authy
Both these apps are available for all desktop (Linux, Mac, Windows) and mobile
(Android and iOS) platforms. They also work directly in your web browser without
you needing to install anything.
More products are in the pipeline.
## History
Ente was the founded by Vishnu Mohandas (he's also Ente's CEO) in response to
privacy concerns with major tech companies. The underlying motivation was the
understanding that big tech had no incentive to fix their act, but with
end-to-end encrypted cross platform apps, there was a way for people to take
back control over their own data without sacrificing on features.
### What does Ente mean?
In Malayalam, Vishnu's native language, "ente" means "mine". Thus "Ente Photos"
has the literal meaning "my photos".
This was a good name, but still Vishnu looked around for better ones. But one
day, he discovered that "ente" means "duck" in German. This unexpected
connection sealed the deal. We should ask him why he likes ducks so much, but
apparently he does, so this dual meaning ("mine" / "duck") led him to finalize
the name, and also led to the adoption of "Ducky", Ente's mascot:
<div align="center">
![Ente's mascot, Ducky](ducky.png){width=200px}
</div>
For the full origin story of Ducky you can check out
[this blog post](https://ente.io/blog/ducky/).

View File

@@ -47,20 +47,8 @@ device.
The indexes are synced across all your devices automatically using the same
end-to-end encrypted security that we use for syncing your photos.
---
#### Local indexing on mobile
In general the machine learning is optimized to work well on most mobile device.
However, devices with low RAM (4-6GB) and large photo libraries might struggle
to complete the indexing without affecting performance of the app. In such case,
you might want to disable local indexing and let the desktop run it instead.
You can disable local indexing from the settings, under
`General > Advanced > Machine learning > Configuration`. This way, you can
continue to use the ML features without your phone performance taking any hit.
---
Note that the desktop app does not currently support modifying the face
groupings, that is only supported by the mobile app.
For more information on how to use Machine Learning for face recognition please
check out [the FAQ](../faq/face-recognition).

View File

@@ -1,33 +0,0 @@
---
title: Notifications
description: Details about notifications in Ente
---
# Notifications
The Ente app can send notifications to notify you of an update, or just to
remind you of some sweet or helpful memory at the right time.
## New shared photos
Receive notifications when someone adds a photo to a shared album that you're a
part of.
## "On this day" memories
Receive reminders about memories from this day in previous years. These
reminders will only be shown if there are enough photos taken across previous
years of the specific day.
## Birthday notifications
Receive reminders when it's someone's birthday. Tapping on the notification will
take you to photos of the birthday person. This requires you to first add a
birthday to a person, and will only be shown if there are enough photos of that
person.
## Notification permission
By default all notification categories are enabled if you give notification
permission. You can disable all of the above notification categories from
`Settings > Notifications`. Notifications currently only work on mobile.

View File

@@ -66,4 +66,5 @@ If you run into any issues during your data export, please reach out to
Note that we also provide a
[CLI tool](https://github.com/ente-io/ente/tree/main/cli#export) to export your
data.
data. You can find more information about the export in the
[export FAQ](/photos/faq/export).

View File

@@ -18,6 +18,13 @@ Before getting start keep the following NOTE in mind.
> and uploading will not work. This is not necessary for those who are not
> behing CGNAT. This guide also work on docker rootless and normal.
> [!CAUTION] Remember that current docker update 28.0.0 has some bug and cannot
> connect to external network. Make sure to install docker-ce 27.5.0,
> docker-ce-rootless-extras 27.5.0 and docker-ce-cli 27.5.0. Hopefully docker
> 28.1.0 will resolve this issue in next week. Refrence links are
> [Moby Github Repo Issues 49511](https://github.com/moby/moby/issues/49511) and
> [Moby Github Repo Issues 49519](https://github.com/moby/moby/issues/49519)
> [!IMPORTANT] For Docker rootless, the user must have local permissions for all
> directories required by the Ente-photos self-hosted server. This can be
> achieved by running `sudo chown -R 1000:1000 /home/ubuntu/docker/ente`. In the
@@ -31,7 +38,7 @@ Before getting start keep the following NOTE in mind.
> net.ipv4.ping_group_range = 0 2147483647
> ```
>
> then run `sudo sysctl --system`. Create
> than run `sudo sysctl --system`. Create
> `~/.config/systemd/user/docker.service.d/override.conf` with the following
> content:
>
@@ -48,7 +55,7 @@ Before getting start keep the following NOTE in mind.
## GETTING START WITH SETUP
First of all create a directory
`sudo mkdir -p /home/ubuntu/docker/tsdproxy/config` then `cd docker/tsdproxy`
`sudo mkdir -p /home/ubuntu/docker/tsdproxy/config` than `cd docker/tsdproxy`
and create compose.yaml file by running `sudo nano compose.yaml`. Populate it
with the following:

View File

@@ -197,7 +197,7 @@ ports). The web server of choice in this guide is
[Caddy](https://caddyserver.com) because with caddy you don't have to manually
configure/setup SSL ceritifcates as caddy will take care of that.
```groovy
```sh
photos.yourdomain.com {
reverse_proxy http://localhost:3001
# for logging
@@ -219,7 +219,6 @@ Next, start the caddy server :).
sudo systemctl enable caddy
sudo systemctl daemon-reload
sudo systemctl start caddy
```

View File

@@ -5,32 +5,20 @@ description: Getting started self hosting Ente Photos and/or Ente Auth
# Self Hosting
The entire source code for Ente is open source,
[including the servers](https://ente.io/blog/open-sourcing-our-server/). This is
The entire source code for Ente is open source, including the servers. This is
the same code we use for our own cloud service.
## Requirements
> [!TIP]
>
> You might find our [blog post](https://ente.io/blog/open-sourcing-our-server/)
> announcing the open sourcing of our server useful.
### Hardware
## System requirements
The server is capable of running on minimal resource requirements as a
lightweight Go binary, since most of the intensive computational tasks are done
on the client. It performs well on small cloud instances, old laptops, and even
The server has minimal resource requirements, running as a lightweight Go
binary. It performs well on small cloud instances, old laptops, and even
[low-end embedded devices](https://github.com/ente-io/ente/discussions/594).
### Software
#### Operating System
Any Linux or \*nix operating system, Ubuntu or Debian is recommended to have a
good Docker experience. Non-Linux operating systems tend to provide poor
experience with Docker and difficulty with troubleshooting and assistance.
#### Docker
Required for running Ente's server, web application and dependent services
(database and object storage)
## Getting started
Run this command on your terminal to setup Ente.
@@ -40,17 +28,12 @@ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ente-io/ente/main/server/q
```
The above `curl` command pulls the Docker image, creates a directory `my-ente`
in the current working directory, prompts to start the cluster and starts all the containers required to run Ente.
in the current working directory and starts all containers required to run Ente.
![quickstart](/quickstart.png)
![self-hosted-ente](/web-app.webp)
> [!TIP] Important:
> If you have used quickstart for self-hosting Ente and are facing issues while > trying to run the cluster due to MinIO buckets not being created, please check [troubleshooting MinIO](/self-hosting/troubleshooting/docker#minio-provisioning-error)
>
>
## Queries?
If you need support, please ask on our community

View File

@@ -32,17 +32,10 @@ After the installation is complete, a `Caddyfile` is created on the path
`/etc/caddy/`. This file is used to configure reverse proxies among other
things.
```groovy
```yaml
# Caddyfile - myente.xyz is just an example.
api.myente.xyz {
reverse_proxy http://localhost:8080
}
ente.myente.xyz {
reverse_proxy http://localhost:3000
}
api.myente.xyz { reverse_proxy http://localhost:8080 } ente.myente.xyz {
reverse_proxy http://localhost:3000 }
#...and so on for other endpoints
```

View File

@@ -46,7 +46,7 @@ minio-provision:
sh -c '
#!/bin/sh
while ! mc alias set h0 http://minio:3200 your_minio_user your_minio_pass
while ! mc config host add h0 http://minio:3200 changeme changeme1234
do
echo "waiting for minio..."
sleep 0.5
@@ -135,43 +135,3 @@ If you're unsure about removing volumes, another alternative is to rename your
`my-ente` folder. Docker uses the folder name to determine the volume name
prefix, so giving it a different name will cause Docker to create a volume
afresh for it.
## MinIO provisioning error
If you have used our quickstart script for self-hosting Ente (new users will be unaffected) and are using the default MinIO container for object storage, you may run into issues while starting the cluster after pulling latest images with provisioning MinIO and creating buckets.
You may encounter similar logs while trying to start the cluster:
```
my-ente-minio-1 -> | Waiting for minio...
my-ente-minio-1 -> | Waiting for minio...
my-ente-minio-1 -> | Waiting for minio...
```
MinIO has deprecated the `mc config` command in favor of `mc alias set` resulting in failure in execution of the command for creating bucket using `post_start` hook.
This can be resolved by changing `mc config host h0 add http://minio:3200 $minio_user $minio_pass` to `mc alias set h0 http://minio:3200 $minio_user $minio_pass`
Thus the updated `post_start` will look as follows for `minio` service:
``` yaml
minio:
...
post_start:
- command: |
sh -c '
#!/bin/sh
while ! mc alias set h0 http://minio:3200 your_minio_user your_minio_pass 2>/dev/null
do
echo "Waiting for minio..."
sleep 0.5
done
cd /data
mc mb -p b2-eu-cen
mc mb -p wasabi-eu-central-2-v3
mc mb -p scw-eu-fr-v3
'
```

View File

@@ -46,25 +46,27 @@ You can alternatively install the build from PlayStore or F-Droid.
## 🧑‍💻 Building from source
1. [Install Flutter v3.24.3](https://flutter.dev/docs/get-started/install).
1. Install [Flutter v3.24.3](https://flutter.dev/docs/get-started/install) and [Rust v1.85.1](https://www.rust-lang.org/tools/install).
2. Pull in all submodules with `git submodule update --init --recursive`
2. Install [Flutter Rust Bridge](https://cjycode.com/flutter_rust_bridge/) with `cargo install flutter_rust_bridge_codegen`
3. Enable repo git hooks `git config core.hooksPath hooks`
3. Pull in all submodules with `git submodule update --init --recursive`
4. If using Visual Studio Code, add the [Flutter
4. Enable repo git hooks `git config core.hooksPath hooks`
5. If using Visual Studio Code, add the [Flutter
Intl](https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl)
extension
5. On Android:
6. On Android:
* For development, run `flutter run -t lib/main.dart --flavor independent`
- For development, run `flutter run -t lib/main.dart --flavor independent`
* For building APK, [setup your
- For building APK, [setup your
keystore](https://docs.flutter.dev/deployment/android#create-an-upload-keystore)
and run `flutter build apk --release --flavor independent`
6. For iOS, run `flutter build ios`
7. For iOS, run `flutter build ios`
Some common issues and troubleshooting tips are in [docs/dev](docs/dev.md).
@@ -88,11 +90,12 @@ issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language
to have it added.
## Certificate Fingerprints
- **SHA1**: E1:60:10:18:B6:B0:2E:A3:74:6F:90:67:50:30:29:75:0E:EF:6D:39
- **SHA256**: 35:ED:56:81:B7:0B:B3:BD:35:D9:0D:85:6A:F5:69:4C:50:4D:EF:46:AA:D8:3F:77:7B:1C:67:5C:F4:51:35:0B
To verify these fingerprints, use the following command:
```bash
apksigner verify --print-certs <path_to_apk>
```

View File

@@ -31,7 +31,7 @@ if (keystorePropertiesFile.exists()) {
android {
namespace = "io.ente.photos"
compileSdk = 35
ndkVersion = flutter.ndkVersion
ndkVersion = "28.0.13004108"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
@@ -142,14 +142,4 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
constraints {
implementation("androidx.work:work-runtime:2.8.1") {
because("Align work-runtime versions")
}
implementation("androidx.work:work-runtime-ktx:2.8.1") {
because("Align work-runtime-ktx versions")
}
}
}

View File

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

View File

@@ -9,6 +9,9 @@ allprojects {
jcenter()
mavenCentral()
// mavenLocal() // for FDroid
maven {
url "${project(':background_fetch').projectDir}/libs"
}
maven {
url "${project(':ffmpeg_kit_flutter').projectDir}/libs"
}

View File

@@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75 6.75H21.75M6.75 12H21.75M6.75 17.25H21.75" stroke="white" stroke-opacity="0.6" stroke-width="2.25" stroke-linejoin="round"/>
<path d="M3 6H4.5V7.5H3V6ZM3 11.25H4.5V12.75H3V11.25ZM3 16.5H4.5V18H3V16.5Z" stroke="white" stroke-opacity="0.6" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 425 B

View File

@@ -1,4 +0,0 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.75 6.75H21.75M6.75 12H21.75M6.75 17.25H21.75" stroke="black" stroke-opacity="0.6" stroke-width="2.25" stroke-linejoin="round"/>
<path d="M3 6H4.5V7.5H3V6ZM3 11.25H4.5V12.75H3V11.25ZM3 16.5H4.5V18H3V16.5Z" stroke="black" stroke-opacity="0.6" stroke-width="1.5" stroke-linecap="square" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 425 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7548 14.394L12.1277 10.7669C13.0009 9.60436 13.4723 8.18933 13.4707 6.73536C13.4707 3.02151 10.4492 0 6.73536 0C3.02151 0 0 3.02151 0 6.73536C0 10.4492 3.02151 13.4707 6.73536 13.4707C8.18933 13.4723 9.60436 13.0009 10.7669 12.1277L14.394 15.7548C14.5776 15.9189 14.8171 16.0065 15.0632 15.9996C15.3094 15.9927 15.5436 15.8919 15.7177 15.7177C15.8919 15.5436 15.9927 15.3094 15.9996 15.0632C16.0065 14.8171 15.9189 14.5776 15.7548 14.394ZM1.92439 6.73536C1.92439 5.78384 2.20655 4.85369 2.73518 4.06253C3.26382 3.27137 4.01519 2.65473 4.89428 2.2906C5.77337 1.92647 6.7407 1.8312 7.67393 2.01683C8.60717 2.20246 9.4644 2.66066 10.1372 3.33349C10.8101 4.00632 11.2683 4.86355 11.4539 5.79679C11.6395 6.73002 11.5442 7.69735 11.1801 8.57644C10.816 9.45553 10.1994 10.2069 9.40819 10.7355C8.61703 11.2642 7.68688 11.5463 6.73536 11.5463C5.45988 11.5448 4.23708 11.0374 3.33518 10.1355C2.43328 9.23364 1.92592 8.01084 1.92439 6.73536Z" fill="white" fill-opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7548 14.394L12.1277 10.7669C13.0009 9.60436 13.4723 8.18933 13.4707 6.73536C13.4707 3.02151 10.4492 0 6.73536 0C3.02151 0 0 3.02151 0 6.73536C0 10.4492 3.02151 13.4707 6.73536 13.4707C8.18933 13.4723 9.60436 13.0009 10.7669 12.1277L14.394 15.7548C14.5776 15.9189 14.8171 16.0065 15.0632 15.9996C15.3094 15.9927 15.5436 15.8919 15.7177 15.7177C15.8919 15.5436 15.9927 15.3094 15.9996 15.0632C16.0065 14.8171 15.9189 14.5776 15.7548 14.394ZM1.92439 6.73536C1.92439 5.78384 2.20655 4.85369 2.73518 4.06253C3.26382 3.27137 4.01519 2.65473 4.89428 2.2906C5.77337 1.92647 6.7407 1.8312 7.67393 2.01683C8.60717 2.20246 9.4644 2.66066 10.1372 3.33349C10.8101 4.00632 11.2683 4.86355 11.4539 5.79679C11.6395 6.73002 11.5442 7.69735 11.1801 8.57644C10.816 9.45553 10.1994 10.2069 9.40819 10.7355C8.61703 11.2642 7.68688 11.5463 6.73536 11.5463C5.45988 11.5448 4.23708 11.0374 3.33518 10.1355C2.43328 9.23364 1.92592 8.01084 1.92439 6.73536Z" fill="black" fill-opacity="0.6"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -10,12 +10,7 @@
"type": "dart",
"flutterMode": "debug",
"program": "mobile/lib/main.dart",
"args": [
"--flavor",
"independent",
"--dart-define",
"cronetHttpNoPlay=true"
]
"args": ["--flavor", "independent"]
},
{
"name": "Photos Android Local",
@@ -29,9 +24,7 @@
"--dart-define",
"endpoint=http://localhost:8080",
"--dart-define",
"web-family=http://localhost:3003",
"--dart-define",
"cronetHttpNoPlay=true"
"web-family=http://localhost:3003"
]
},
{

View File

@@ -1,36 +0,0 @@
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

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

View File

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

View File

@@ -4,33 +4,33 @@ Se busca por uma alternativa do Google Fotos baseada em privacidade, chegaste ao
Temos aplicações de fonte aberta para Android, iOS, sítio web e desktop, e as fotos serão perfeitamente sincronizadas entre todas elas numa maneira de encriptação de ponta a ponta (e2ee).
Ente facilita o partilhamento dos seus álbuns com entes queridos, mesmo se não estiverem no ente. Pode partilhar ligações visíveis a público, onde eles podem ver o seu álbum e colaborar a adicionar fotos, mesmo sem uma conta ou a aplicação.
Ente também simplifica a partilha dos seus álbuns com os seus entes queridos, mesmo que estes não estejam no ente. Pode partilhar ligações visíveis publicamente, onde podem ver o seu álbum e colaborar adicionando fotografias ao mesmo, mesmo sem uma conta ou aplicação.
Os dados são replicados em 3 localizações diferentes, incluindo um posto em Paris. Levamos a nossa postura a sério e facilitamos para certificarmos que as suas memórias revivam-no.
Os seus dados encriptados são replicados em 3 locais diferentes, incluindo um abrigo de emergência em Paris. Levamos a posteridade a sério e facilitamos a tarefa de garantir que as suas memórias perdurem para além de si.
Estamos aqui para fazer a aplicação mais segura do mundo, venha e adere a nossa jornada!
Estamos aqui para criar a aplicação de fotografias mais segura de sempre, junte-se à nossa viagem!
FUNCIONALIDADES
- Backups com qualidade original, por cada píxel valer a pena
- Planos em família, para poder partilhar armazenamento com familiares
- Álbuns de colaboração, para unir fotos depois de uma caminhada
- Pastas partilhadas, se quiser que o seu parceiro desfrute dos seus cliques na "Câmara"
- Ligações para álbuns, que podem ser protegidos com uma palavra-passe
- Possibilidade de liberar espaço, removendo ficheiros que já foram feitos backup
- Suporte físico, por valer a pena
- Descrições, para entender as suas memórias e encontrá-las facilmente
- Editor de imagens, para dar retoques finais
- Adicionar aos favoritos, obscurecer e reviver as suas memórias, para aqueles tão preciosos
- Importar num só clique do Google, Apple, e o seu disco rígido e mais
- Tema escuro, para as suas fotos encaixarem melhor
RECURSOS
- Cópias de segurança de qualidade original, porque cada pixel é importante
- Planos familiares, para que possa partilhar o armazenamento com a sua família
- Álbuns colaborativos, para que possa reunir fotos depois de uma viagem
- Pastas partilhadas, caso queira que o seu parceiro desfrute dos seus cliques na Câmara
- Links para álbuns, que podem ser protegidas com uma palavra-passe
- Capacidade de libertar espaço, removendo ficheiros dos quais foi feita uma cópia de segurança segura
- Apoio humano, porque vale a pena
- Descrições, para que possa legendar as suas memórias e encontrá-las facilmente
- Editor de imagens, para dar os retoques finais
- Favoritar, ocultar e reviver suas memórias, pois elas são preciosas
- Importação com um clique do Google, da Apple, do seu disco rígido e muito mais
- Tema escuro, porque as suas fotos ficam bem com ele
- 2FA, 3FA, autenticação biométrica
- e MAIS além!
- e MUITO mais!
PERMISSÕES
Ente pede por certas permissões para servir o propósito dum provedor de armazenamento de foto, onde pode ser revisto aqui: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
ente solicita determinadas permissões para servir o objetivo de um fornecedor de armazenamento de fotografias, que pode ser consultado aqui: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
PREÇO
Não garantimos planos gratuitos para sempre, que é importante a nós mantermo-nos sustentáveis e conseguirmos superar o desafio do tempo. Ao invés, garantimos planos acessíveis para poder partilhar livremente com os seus familiares. Para mais informações, consulte "ente.io"
Não oferecemos planos gratuitos para sempre, porque é importante para nós mantermo-nos sustentáveis e resistirmos ao teste do tempo. Em vez disso, oferecemos planos acessíveis que pode partilhar livremente com a sua família. Pode encontrar mais informações em ente.io.
SUPORTE
Estamos orgulhosos ao oferecer suporte físico. Se for um cliente pago, pode contactar a nossa equipa através de "team@ente.io" e esperar uma resposta nossa dentro de um dia.
SUPPORT
Orgulhamo-nos de oferecer um apoio humano Se for nosso cliente pago, pode contactar team@ente.io e esperar uma resposta da nossa equipa no prazo de 24 horas.

View File

@@ -1,33 +0,0 @@
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

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

View File

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

View File

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

View File

@@ -1,30 +0,0 @@
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

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

View File

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

View File

@@ -2,29 +2,29 @@ Ente é uma aplicação simples feita para fazer backup automaticamente e organi
Se busca por uma alternativa mais privada para preservar as suas memórias, chegaste ao lugar correto. Com Ente, eles são armazenados em encriptação de ponta a ponta (e2ee). Isto significa que só vos podeis vê-las.
Nós temos aplicações em Android, iOS, Sítio Web e Desktop, todas as suas fotos são sincronizadas sem parar entre os aparelhos numa forma de encriptação (e2ee).
Temos aplicações para Android, iOS, Web e ambiente de trabalho, e as suas fotografias serão perfeitamente sincronizadas entre todos os seus dispositivos de uma forma encriptada de ponta a ponta (e2ee).
Ente também facilita a partilha de álbuns com entes queridos. É possível partilhá-los diretamente com outros utilizadores, em encriptação ponta-a-ponta; ou com ligações visíveis a público.
O Ente também simplifica a partilha dos seus álbuns com os seus entes queridos Pode partilhá-los diretamente com outros utilizadores do Ente, encriptados de ponta a ponta, ou com ligações publicamente visíveis.
Os seus dados encriptados são armazenados em várias localizações, incluindo um abrigo avançado em Paris. Levamos a nossa postura a sério e facilitamos para certificarmos que as suas memórias revivam-no.
Os seus dados encriptados são armazenados em vários locais, incluindo um abrigo de emergência em Paris. Levamos a posteridade a sério e facilitamos a tarefa de garantir que as suas memórias perdurem para além de si.
Estamos aqui para fazer a aplicação mais segura do mundo, venha e adere a nossa jornada!
Estamos aqui para criar a aplicação de fotografias mais segura de sempre, junte-se à nossa viagem!
FUNCIONALIDADES
- Backups com qualidade original, por cada píxel valer a pena
- Planos em família, para poder partilhar armazenamento com familiares
- Pastas partilhadas, para que o seu parceiro desfrute de cliques na "Câmara"
- Links de álbuns, para poder ser protegido por uma palavra-passe e definido para expiração
- Possibilidade de liberar espaço, removendo ficheiros que já foram feitos backup
- Editor de imagens, para dar retoques finais
- Adicionar aos favoritos, obscurecer e reviver as suas memórias, para aqueles tão preciosos
- Importar num só clique do Google, Apple, e o seu disco rígido e mais
- Tema escuro, para as suas fotos encaixarem melhor
RECURSOS
- Cópias de segurança de qualidade original, porque cada pixel é importante
- Planos familiares, para que possa partilhar o armazenamento com a sua família
- Pastas partilhadas, caso queira que o seu parceiro desfrute dos seus cliques na Câmara
- Links para álbuns, que podem ser protegidas com uma palavra-passe e definidas para expirar
- Capacidade de libertar espaço, removendo ficheiros dos quais foi feita uma cópia de segurança segura
- Editor de imagens, para dar os retoques finais
- Favoritar, ocultar e reviver suas memórias, pois elas são preciosas
- Importação com um clique do Google, da Apple, do seu disco rígido e muito mais
- Tema escuro, porque as suas fotografias ficam bem com ele
- 2FA, 3FA, autenticação biométrica
- e MAIS além!
- and a LOT more!
💲 PREÇOS
Não garantimos planos gratuitos para sempre, que é importante a nós mantermo-nos sustentáveis e conseguirmos superar o desafio do tempo. Ao invés, garantimos planos acessíveis para poder partilhar livremente com os seus familiares. Para mais informações, consulte "ente.io"
Não oferecemos planos gratuitos para sempre, porque é importante para nós mantermo-nos sustentáveis e resistirmos ao teste do tempo. Em vez disso, oferecemos planos acessíveis que pode partilhar livremente com a sua família. Pode encontrar mais informações em ente.io.
🙋 SUPORTE
Estamos orgulhosos ao oferecer suporte físico. Se for um cliente pago, pode contactar a nossa equipa através de "team@ente.io" e esperar uma resposta nossa dentro de um dia.
🙋 SUPPORT
Orgulhamo-nos de oferecer um apoio humano. Se for nosso cliente pago, pode contactar team@ente.io e esperar uma resposta da nossa equipa no prazo de 24 horas.

View File

@@ -0,0 +1,8 @@
rust_input: crate::api
rust_root: rust/
dart_output: lib/src/rust
dart_preamble: |
// ignore_for_file: require_trailing_commas
web: false

View File

@@ -91,10 +91,9 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
await tester.pumpAndSettle();
}
///Use this widget as floating action buttom in HomeWidget so that frames
///are built and rendered continuously so that timeline trace has continuous
///data. Change the duraiton in `_startTimer()` to control the duraiton of
///are built and rendered continuously so that timeline trace has continuous
///data. Change the duraiton in `_startTimer()` to control the duraiton of
///test on app init.
// class TempWidget extends StatefulWidget {
@@ -127,4 +126,4 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
// ? const CircularProgressIndicator()
// : const SizedBox.shrink();
// }
// }
// }

View File

@@ -0,0 +1,13 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import "package:photos/src/rust/api/simple.dart";
import 'package:photos/src/rust/frb_generated.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() async => await RustLib.init());
testWidgets('Can call rust function', (WidgetTester tester) async {
final testString = greet(name: "Tom");
expect(testString.contains('Tom'), true);
});
}

View File

@@ -1,6 +1,8 @@
PODS:
- app_links (0.0.2):
- Flutter
- background_fetch (1.3.7):
- Flutter
- battery_info (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
@@ -80,28 +82,28 @@ PODS:
- GoogleDataTransport (10.1.0):
- nanopb (~> 3.30910.0)
- PromisesObjC (~> 2.4)
- GoogleUtilities/AppDelegateSwizzler (8.1.0):
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Logger
- GoogleUtilities/Network
- GoogleUtilities/Privacy
- GoogleUtilities/Environment (8.1.0):
- GoogleUtilities/Environment (8.0.2):
- GoogleUtilities/Privacy
- GoogleUtilities/Logger (8.1.0):
- GoogleUtilities/Logger (8.0.2):
- GoogleUtilities/Environment
- GoogleUtilities/Privacy
- GoogleUtilities/Network (8.1.0):
- GoogleUtilities/Network (8.0.2):
- GoogleUtilities/Logger
- "GoogleUtilities/NSData+zlib"
- GoogleUtilities/Privacy
- GoogleUtilities/Reachability
- "GoogleUtilities/NSData+zlib (8.1.0)":
- "GoogleUtilities/NSData+zlib (8.0.2)":
- GoogleUtilities/Privacy
- GoogleUtilities/Privacy (8.1.0)
- GoogleUtilities/Reachability (8.1.0):
- GoogleUtilities/Privacy (8.0.2)
- GoogleUtilities/Reachability (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- GoogleUtilities/UserDefaults (8.1.0):
- GoogleUtilities/UserDefaults (8.0.2):
- GoogleUtilities/Logger
- GoogleUtilities/Privacy
- home_widget (0.0.1):
@@ -154,7 +156,7 @@ PODS:
- nanopb/encode (= 3.30910.0)
- nanopb/decode (3.30910.0)
- nanopb/encode (3.30910.0)
- native_video_player (4.0.0):
- native_video_player (1.0.0):
- Flutter
- objective_c (0.0.1):
- Flutter
@@ -184,14 +186,16 @@ PODS:
- PromisesObjC (2.4.0)
- receive_sharing_intent (1.8.1):
- Flutter
- SDWebImage (5.21.1):
- SDWebImage/Core (= 5.21.1)
- SDWebImage/Core (5.21.1)
- rust_lib_photos (0.0.1):
- Flutter
- SDWebImage (5.21.0):
- SDWebImage/Core (= 5.21.0)
- SDWebImage/Core (5.21.0)
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- Sentry/HybridSDK (8.46.0)
- sentry_flutter (8.14.2):
- sentry_flutter (8.14.1):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.46.0)
@@ -203,16 +207,16 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- sqlite3 (3.49.2):
- sqlite3/common (= 3.49.2)
- sqlite3/common (3.49.2)
- sqlite3/dbstatvtab (3.49.2):
- sqlite3 (3.49.1):
- sqlite3/common (= 3.49.1)
- sqlite3/common (3.49.1)
- sqlite3/dbstatvtab (3.49.1):
- sqlite3/common
- sqlite3/fts5 (3.49.2):
- sqlite3/fts5 (3.49.1):
- sqlite3/common
- sqlite3/perf-threadsafe (3.49.2):
- sqlite3/perf-threadsafe (3.49.1):
- sqlite3/common
- sqlite3/rtree (3.49.2):
- sqlite3/rtree (3.49.1):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
@@ -240,11 +244,10 @@ PODS:
- Flutter
- wakelock_plus (0.0.1):
- Flutter
- workmanager (0.0.1):
- Flutter
DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- background_fetch (from `.symlinks/plugins/background_fetch/ios`)
- battery_info (from `.symlinks/plugins/battery_info/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
@@ -288,6 +291,7 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- rust_lib_photos (from `.symlinks/plugins/rust_lib_photos/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@@ -301,7 +305,6 @@ DEPENDENCIES:
- video_thumbnail (from `.symlinks/plugins/video_thumbnail/ios`)
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- workmanager (from `.symlinks/plugins/workmanager/ios`)
SPEC REPOS:
https://github.com/ente-io/ffmpeg-kit-custom-repo-ios.git:
@@ -329,6 +332,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
background_fetch:
:path: ".symlinks/plugins/background_fetch/ios"
battery_info:
:path: ".symlinks/plugins/battery_info/ios"
connectivity_plus:
@@ -415,6 +420,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/privacy_screen/ios"
receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios"
rust_lib_photos:
:path: ".symlinks/plugins/rust_lib_photos/ios"
sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios"
share_plus:
@@ -441,11 +448,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/volume_controller/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
workmanager:
:path: ".symlinks/plugins/workmanager/ios"
SPEC CHECKSUMS:
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2
battery_info: b6c551049266af31556b93c9d9b9452cfec0219f
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
@@ -472,7 +478,7 @@ SPEC CHECKSUMS:
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
in_app_purchase_storekit: a1ce04056e23eecc666b086040239da7619cd783
@@ -490,7 +496,7 @@ SPEC CHECKSUMS:
motionphoto: 8b65ce50c7d7ff3c767534fc3768b2eed9ac24e4
move_to_background: cd3091014529ec7829e342ad2d75c0a11f4378a5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: 29ab24a926804ac8c4a57eb6d744c7d927c2bc3e
native_video_player: 5d36066807b680e181473e6890dde643ac85380d
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
@@ -503,15 +509,16 @@ SPEC CHECKSUMS:
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
rust_lib_photos: 04d3901908d2972192944083310b65abf410774c
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
SDWebImage: f29024626962457f3470184232766516dee8dfea
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
sentry_flutter: 6a134f9d381e49f22ea25a67736cf0cf4d02ec9c
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: 3c950dc86011117c307eb0b28c4a7bb449dce9f1
sqlite3: fc1400008a9b3525f5914ed715a5d1af0b8f4983
sqlite3_flutter_libs: 069c435986dd4b63461aecd68f4b30be4a9e9daa
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
thermal: a9261044101ae8f532fa29cab4e8270b51b3f55c
@@ -521,7 +528,6 @@ SPEC CHECKSUMS:
video_thumbnail: 94ba6705afbaa120b77287080424930f23ea0c40
volume_controller: 2e3de73d6e7e81a0067310d17fb70f2f86d71ac7
wakelock_plus: 373cfe59b235a6dd5837d0fb88791d2f13a90d56
workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6
PODFILE CHECKSUM: a8ef88ad74ba499756207e7592c6071a96756d18

View File

@@ -527,6 +527,7 @@
"${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/app_links/app_links.framework",
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
"${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/cupertino_http/cupertino_http.framework",
@@ -566,6 +567,7 @@
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
"${BUILT_PRODUCTS_DIR}/rust_lib_photos/rust_lib_photos.framework",
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
@@ -580,7 +582,6 @@
"${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework",
"${BUILT_PRODUCTS_DIR}/volume_controller/volume_controller.framework",
"${BUILT_PRODUCTS_DIR}/wakelock_plus/wakelock_plus.framework",
"${BUILT_PRODUCTS_DIR}/workmanager/workmanager.framework",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg_kit_custom/ffmpegkit.framework/ffmpegkit",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg_kit_custom/libavcodec.framework/libavcodec",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ffmpeg_kit_custom/libavdevice.framework/libavdevice",
@@ -623,6 +624,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImageWebPCoder.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/app_links.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cupertino_http.framework",
@@ -662,6 +664,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rust_lib_photos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
@@ -676,7 +679,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/volume_controller.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/workmanager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ffmpegkit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavcodec.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libavdevice.framework",

View File

@@ -2,7 +2,6 @@ import AVFoundation
import Flutter
import UIKit
import app_links
import workmanager
@main
@objc class AppDelegate: FlutterAppDelegate {
@@ -16,14 +15,6 @@ import workmanager
}
GeneratedPluginRegistrant.register(with: self)
WorkmanagerPlugin.setPluginRegistrantCallback { registry in
GeneratedPluginRegistrant.register(with: registry)
}
var freqInMinutes = 30 * 60
// Register a periodic task in iOS 13+
WorkmanagerPlugin.registerPeriodicTask(
withIdentifier: "io.ente.frame.iOSBackgroundAppRefresh",
frequency: NSNumber(value: freqInMinutes))
// Retrieve the link from parameters
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {

View File

@@ -4,7 +4,7 @@
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>io.ente.frame.iOSBackgroundAppRefresh</string>
<string>com.transistorsoft.fetch</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>

View File

@@ -2,6 +2,7 @@ import "dart:async";
import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
@@ -23,15 +24,18 @@ import "package:photos/services/people_home_widget_service.dart";
import 'package:photos/services/sync/sync_service.dart';
import 'package:photos/ui/tabs/home_widget.dart';
import "package:photos/ui/viewer/actions/file_viewer.dart";
import "package:photos/utils/bg_task_utils.dart";
import "package:photos/utils/intent_util.dart";
import "package:photos/utils/standalone/debouncer.dart";
class EnteApp extends StatefulWidget {
final Future<void> Function(String) runBackgroundTask;
final Future<void> Function(String) killBackgroundTask;
final AdaptiveThemeMode? savedThemeMode;
final Locale? locale;
const EnteApp(
this.runBackgroundTask,
this.killBackgroundTask,
this.locale,
this.savedThemeMode, {
super.key,
@@ -47,9 +51,9 @@ class EnteApp extends StatefulWidget {
}
class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
final _logger = Logger("EnteAppState");
late Locale? locale;
late StreamSubscription<MemoriesChangedEvent> _memoriesChangedSubscription;
final _logger = Logger("EnteAppState");
late StreamSubscription<PeopleChangedEvent> _peopleChangedSubscription;
late Debouncer _changeCallbackDebouncer;
@@ -75,7 +79,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
(event) async {
_changeCallbackDebouncer.run(
() async =>
unawaited(PeopleHomeWidgetService.instance.checkPeopleChanged()),
unawaited(PeopleHomeWidgetService.instance.peopleChanged()),
);
},
);
@@ -108,7 +112,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
: MediaExtentionAction(action: IntentAction.main);
AppLifecycleService.instance.setMediaExtensionAction(mediaExtentionAction);
if (mediaExtentionAction.action == IntentAction.main) {
await BgTaskUtils.configureWorkmanager();
_configureBackgroundFetch();
}
}
@@ -193,4 +197,29 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
AppLifecycleService.instance.onAppInBackground(stateChangeReason);
}
}
void _configureBackgroundFetch() {
BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
forceAlarmManager: false,
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
requiresBatteryNotLow: true,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY,
), (String taskId) async {
await widget.runBackgroundTask(taskId);
}, (taskId) {
_logger.info("BG task timeout taskID: $taskId");
widget.killBackgroundTask(taskId);
}).then((int status) {
_logger.info('[BackgroundFetch] configure success: $status');
}).catchError((e) {
_logger.info('[BackgroundFetch] configure ERROR: $e');
});
}
}

View File

@@ -1,4 +1,3 @@
import "dart:io";
import 'package:photos/core/cache/lru_map.dart';

View File

@@ -75,7 +75,6 @@ class Configuration {
late FlutterSecureStorage _secureStorage;
late String _tempDocumentsDirPath;
late String _thumbnailCacheDirectory;
late String _personFaceThumbnailCacheDirectory;
late String _sharedDocumentsMediaDirectory;
String? _volatilePassword;
@@ -96,9 +95,6 @@ class Configuration {
final tempDirectoryPath = (await getTemporaryDirectory()).path;
_thumbnailCacheDirectory = tempDirectoryPath + "/thumbnail-cache";
Directory(_thumbnailCacheDirectory).createSync(recursive: true);
_personFaceThumbnailCacheDirectory =
_documentsDirectory + "/person-face-thumbnail-cache";
Directory(_personFaceThumbnailCacheDirectory).createSync(recursive: true);
_sharedDocumentsMediaDirectory =
_documentsDirectory + "/ente-shared-media";
Directory(_sharedDocumentsMediaDirectory).createSync(recursive: true);
@@ -553,10 +549,6 @@ class Configuration {
return _thumbnailCacheDirectory;
}
String getPersonFaceThumbnailCacheDirectory() {
return _personFaceThumbnailCacheDirectory;
}
String getSharedMediaDirectory() {
return _sharedDocumentsMediaDirectory;
}

View File

@@ -47,14 +47,6 @@ extension SuperLogRecord on LogRecord {
var msg = "$header $message";
if (error != null) {
if (error is DioException) {
final String? id = (error as DioException)
.requestOptions
.headers['x-request-id'] as String?;
if (id != null) {
msg += "\n⤷ id: $id";
}
}
msg += "\n⤷ type: ${error.runtimeType}\n⤷ error: $error";
}
if (stackTrace != null) {
@@ -187,7 +179,7 @@ class SuperLogging {
setupSentry().ignore();
}
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen(onLogRecord);
if (isFDroidClient) {

View File

@@ -58,7 +58,7 @@ bool isHandledSyncError(Object errObj) {
class LockAlreadyAcquiredError extends Error {}
class LockFreedError extends Error{}
class LockFreedError extends Error {}
class UnauthorizedError extends Error {}

View File

@@ -1,13 +1,10 @@
import "dart:io";
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:photos/core/configuration.dart';
import "package:photos/models/base/id.dart";
import 'package:uuid/uuid.dart';
class EnteRequestInterceptor extends Interceptor {
final String enteEndpoint;
final String id = Platform.isIOS ? "ios" : "droid";
EnteRequestInterceptor(this.enteEndpoint);
@@ -20,7 +17,7 @@ class EnteRequestInterceptor extends Interceptor {
);
}
// ignore: prefer_const_constructors
options.headers.putIfAbsent("x-request-id", () => newID(id));
options.headers.putIfAbsent("x-request-id", () => Uuid().v4().toString());
final String? tokenValue = Configuration.instance.getToken();
if (tokenValue != null) {
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);

View File

@@ -1,7 +1,7 @@
import 'dart:io';
import 'package:dio/dio.dart';
import "package:native_dio_adapter/native_dio_adapter.dart";
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:package_info_plus/package_info_plus.dart';
import "package:photos/core/configuration.dart";
import "package:photos/core/event_bus.dart";

View File

@@ -206,14 +206,14 @@ extension DeviceFiles on FilesDB {
});
if (rowUpdated > 0) {
_logger.info("Updated $rowUpdated rows for ${pathEntity.name}");
_logger.fine("Updated $rowUpdated rows for ${pathEntity.name}");
hasUpdated = true;
}
} else {
hasUpdated = true;
await db.execute(
'''
INSERT INTO device_collections (id, name, count, cover_id, should_backup)
INSERT INTO device_collections (id, name, count, cover_id, should_backup)
VALUES (?, ?, ?, ?, ?);
''',
[
@@ -267,8 +267,8 @@ extension DeviceFiles on FilesDB {
final db = await sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT collection_id FROM device_collections where should_backup =
$_sqlBoolTrue
SELECT collection_id FROM device_collections where should_backup =
$_sqlBoolTrue
and collection_id != -1;
''',
);
@@ -338,12 +338,12 @@ extension DeviceFiles on FilesDB {
SELECT *
FROM ${FilesDB.filesTable}
WHERE ${FilesDB.columnLocalID} IS NOT NULL AND
${FilesDB.columnCreationTime} >= $startTime AND
${FilesDB.columnCreationTime} >= $startTime AND
${FilesDB.columnCreationTime} <= $endTime AND
(${FilesDB.columnOwnerID} IS NULL OR ${FilesDB.columnOwnerID} =
$ownerID ) AND
${FilesDB.columnLocalID} IN
(SELECT id FROM device_files where path_id = '${deviceCollection.id}' )
(${FilesDB.columnOwnerID} IS NULL OR ${FilesDB.columnOwnerID} =
$ownerID ) AND
${FilesDB.columnLocalID} IN
(SELECT id FROM device_files where path_id = '${deviceCollection.id}' )
ORDER BY ${FilesDB.columnCreationTime} $order , ${FilesDB.columnModificationTime} $order
''' +
(limit != null ? ' limit $limit;' : ';');
@@ -359,14 +359,14 @@ extension DeviceFiles on FilesDB {
) async {
final db = await sqliteAsyncDB;
const String rawQuery = '''
SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID},
${FilesDB.columnFileSize}
SELECT ${FilesDB.columnLocalID}, ${FilesDB.columnUploadedFileID},
${FilesDB.columnFileSize}
FROM ${FilesDB.filesTable}
WHERE ${FilesDB.columnLocalID} IS NOT NULL AND
(${FilesDB.columnOwnerID} IS NULL OR ${FilesDB.columnOwnerID} = ?)
AND (${FilesDB.columnUploadedFileID} IS NOT NULL AND ${FilesDB.columnUploadedFileID} IS NOT -1)
AND
${FilesDB.columnLocalID} IN
AND
${FilesDB.columnLocalID} IN
(SELECT id FROM device_files where path_id = ?)
''';
final results = await db.getAll(rawQuery, [ownerID, pathID]);
@@ -425,7 +425,7 @@ extension DeviceFiles on FilesDB {
final EnteFile? result =
await getDeviceCollectionThumbnail(deviceCollection.id);
if (result == null) {
_logger.info(
_logger.finest(
'Failed to find coverThumbnail for deviceFolder',
);
continue;
@@ -453,7 +453,7 @@ extension DeviceFiles on FilesDB {
debugPrint("Call fallback method to get potential thumbnail");
final db = await sqliteAsyncDB;
final fileRows = await db.getAll(
'''SELECT * FROM FILES f JOIN device_files df on f.local_id = df.id
'''SELECT * FROM FILES f JOIN device_files df on f.local_id = df.id
and df.path_id= ? order by f.creation_time DESC limit 1;
''',
[pathID],

View File

@@ -77,20 +77,6 @@ extension EntitiesDB on FilesDB {
);
}
Future<List<LocalEntityData>> getCertainEntities(
EntityType type,
List<String> ids,
) async {
final db = await sqliteAsyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT * FROM entities WHERE type = ? AND id IN (${List.filled(ids.length, '?').join(',')})',
[type.name, ...ids],
);
return List.generate(maps.length, (i) {
return LocalEntityData.fromJson(maps[i]);
});
}
Future<List<LocalEntityData>> getEntities(EntityType type) async {
final db = await sqliteAsyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
@@ -113,19 +99,4 @@ extension EntitiesDB on FilesDB {
}
return LocalEntityData.fromJson(maps.first);
}
Future<String?> getPreHashForEntities(
EntityType type,
List<String> ids,
) async {
final db = await sqliteAsyncDB;
final maps = await db.get(
'SELECT GROUP_CONCAT(id || \':\' || updatedAt, \',\') FROM entities WHERE type = ? AND id IN (${List.filled(ids.length, '?').join(',')})',
[type.name, ...ids],
);
if (maps.isEmpty) {
return null;
}
return maps.values.first as String?;
}
}

View File

@@ -1436,6 +1436,22 @@ class FilesDB with SqlDbBase {
return convertToFiles(rows).first;
}
Future<void> markForReUploadIfLocationMissing(List<String> localIDs) async {
if (localIDs.isEmpty) {
return;
}
final inParam = localIDs.map((id) => "'$id'").join(',');
final db = await instance.sqliteAsyncDB;
await db.execute(
'''
UPDATE $filesTable
SET $columnUpdationTime = NULL
WHERE $columnLocalID IN ($inParam)
AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLongitude = 0.0 or $columnLongitude = 0.0);
''',
);
}
Future<bool> doesFileExistInCollection(
int uploadedFileID,
int collectionID,
@@ -1570,6 +1586,25 @@ class FilesDB with SqlDbBase {
return files;
}
// For givenUserID, get List of unique LocalIDs for files which are
// uploaded by the given user and location is missing
Future<List<String>> getLocalIDsForFilesWithoutLocation(int ownerID) async {
final db = await instance.sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT DISTINCT $columnLocalID FROM $filesTable
WHERE $columnOwnerID = ? AND $columnLocalID IS NOT NULL AND
($columnLatitude IS NULL OR $columnLongitude IS NULL OR $columnLatitude = 0.0 or $columnLongitude = 0.0)
''',
[ownerID],
);
final result = <String>[];
for (final row in rows) {
result.add(row[columnLocalID].toString());
}
return result;
}
// For a given userID, return unique uploadedFileId for the given userID
Future<List<int>> getUploadIDsWithMissingSize(int userId) async {
final db = await instance.sqliteAsyncDB;
@@ -1587,6 +1622,25 @@ class FilesDB with SqlDbBase {
return result;
}
Future<List<String>> getLocalFilesBackedUpWithoutLocation(int userId) async {
final db = await instance.sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT DISTINCT $columnLocalID FROM $filesTable
WHERE $columnOwnerID = ? AND $columnLocalID IS NOT NULL AND
($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)
AND ($columnLatitude IS NULL OR $columnLongitude IS NULL OR
$columnLatitude = 0.0 or $columnLongitude = 0.0)
''',
[userId],
);
final result = <String>[];
for (final row in rows) {
result.add(row[columnLocalID] as String);
}
return result;
}
// updateSizeForUploadIDs takes a map of upploadedFileID and fileSize and
// update the fileSize for the given uploadedFileID
Future<void> updateSizeForUploadIDs(

View File

@@ -13,7 +13,6 @@ abstract class IMLDataDB<T> {
Future<int> getFaceIndexedFileCount({int minimumMlVersion});
Future<Map<String, int>> clusterIdToFaceCount();
Future<Set<String>> getPersonIgnoredClusters(String personID);
Future<Map<String, Set<String>>> getPersonToRejectedSuggestions();
Future<Set<String>> getPersonClusterIDs(String personID);
Future<Set<String>> getPersonsClusterIDs(List<String> personID);
Future<void> clearTable();
@@ -41,7 +40,6 @@ abstract class IMLDataDB<T> {
Future<Map<String, Iterable<String>>> getAllClusterIdToFaceIDs();
Future<Iterable<String>> getFaceIDsForCluster(String clusterID);
Future<Map<String, Map<String, Set<String>>>> getPersonToClusterIdToFaceIds();
Future<Map<String, Set<String>>> getPersonToClusterIDs();
Future<Map<String, Set<String>>> getClusterIdToFaceIdsForPerson(
String personID,
);

View File

@@ -0,0 +1,238 @@
import "dart:typed_data" show Float32List;
import "package:flutter_rust_bridge/flutter_rust_bridge.dart" show Uint64List;
import "package:logging/logging.dart";
import "package:path/path.dart";
import "package:path_provider/path_provider.dart";
import "package:photos/models/ml/vector.dart";
import "package:photos/services/machine_learning/semantic_search/query_result.dart";
import "package:photos/src/rust/api/usearch_api.dart";
class ClipVectorDB {
static final Logger _logger = Logger("ClipVectorDB");
static const _databaseName = "ente.ml.vectordb.clip";
static final BigInt _embeddingDimension = BigInt.from(512);
static Logger get logger => _logger;
// Singleton pattern
ClipVectorDB._privateConstructor();
static final instance = ClipVectorDB._privateConstructor();
factory ClipVectorDB() => instance;
// only have a single app-wide reference to the database
static Future<VectorDb>? _vectorDbFuture;
Future<VectorDb> get _vectorDB async {
_vectorDbFuture ??= _initVectorDB();
return _vectorDbFuture!;
}
Future<VectorDb> _initVectorDB() async {
final documentsDirectory = await getApplicationDocumentsDirectory();
final String databaseDirectory =
join(documentsDirectory.path, _databaseName);
_logger.info("Opening vectorDB access: DB path " + databaseDirectory);
final vectorDB = VectorDb(
filePath: databaseDirectory,
dimensions: _embeddingDimension,
);
final stats = await getIndexStats(vectorDB);
_logger.info("VectorDB connection opened with stats: ${stats.toString()}");
return vectorDB;
}
Future<void> insertEmbedding({
required int fileID,
required List<double> embedding,
}) async {
final db = await _vectorDB;
try {
await db.addVector(key: BigInt.from(fileID), vector: embedding);
} catch (e, s) {
_logger.severe("Error inserting embedding", e, s);
rethrow;
}
}
Future<void> bulkInsertEmbeddings({
required List<int> fileIDs,
required List<Float32List> embeddings,
}) async {
final db = await _vectorDB;
final bigKeys = Uint64List.fromList(fileIDs);
try {
await db.bulkAddVectors(keys: bigKeys, vectors: embeddings);
} catch (e, s) {
_logger.severe("Error bulk inserting embeddings", e, s);
rethrow;
}
}
Future<List<EmbeddingVector>> getEmbeddings(List<int> fileIDs) async {
final db = await _vectorDB;
try {
final keys = Uint64List.fromList(fileIDs);
final vectors = await db.bulkGetVectors(keys: keys);
return List.generate(
vectors.length,
(index) => EmbeddingVector(
fileID: fileIDs[index],
embedding: vectors[index],
),
);
} catch (e, s) {
_logger.severe("Error getting embeddings", e, s);
rethrow;
}
}
Future<void> deleteEmbeddings(List<int> fileIDs) async {
final db = await _vectorDB;
try {
final deletedCount =
await db.bulkRemoveVectors(keys: Uint64List.fromList(fileIDs));
_logger.info(
"Deleted $deletedCount embeddings, from ${fileIDs.length} keys",
);
} catch (e, s) {
_logger.severe("Error bulk deleting specific embeddings", e, s);
rethrow;
}
}
Future<void> deleteAllEmbeddings() async {
final db = await _vectorDB;
try {
await db.resetIndex();
} catch (e, s) {
_logger.severe("Error deleting all embeddings", e, s);
rethrow;
}
}
Future<void> deleteIndex() async {
final db = await _vectorDB;
try {
await db.deleteIndex();
_vectorDbFuture = null;
} catch (e, s) {
_logger.severe("Error deleting index", e, s);
rethrow;
}
}
Future<VectorDbStats> getIndexStats([VectorDb? db]) async {
db ??= await _vectorDB;
try {
final stats = await db.getIndexStats();
return VectorDbStats(
size: stats.$1.toInt(),
capacity: stats.$2.toInt(),
dimensions: stats.$3.toInt(),
fileSize: stats.$4.toInt(),
memoryUsage: stats.$5.toInt(),
expansionAdd: stats.$6.toInt(),
expansionSearch: stats.$7.toInt(),
);
} catch (e, s) {
_logger.severe("Error getting index stats", e, s);
rethrow;
}
}
Future<(Uint64List, Float32List)> searchClosestVectors(
List<double> query,
int count,
) async {
final db = await _vectorDB;
try {
final result =
await db.searchVectors(query: query, count: BigInt.from(count));
return result;
} catch (e, s) {
_logger.severe("Error searching closest vectors", e, s);
rethrow;
}
}
Future<(BigInt, double)> searchClosestVector(
List<double> query,
) async {
final db = await _vectorDB;
try {
final result = await db.searchVectors(query: query, count: BigInt.one);
return (result.$1[0], result.$2[0]);
} catch (e, s) {
_logger.severe("Error searching closest vector", e, s);
rethrow;
}
}
Future<Map<String, List<QueryResult>>> computeBulkSimilarities(
Map<String, List<double>> textQueryToEmbeddingMap,
Map<String, double> minimumSimilarityMap,
) async {
try {
final queryToResults = <String, List<QueryResult>>{};
for (final MapEntry<String, List<double>> entry
in textQueryToEmbeddingMap.entries) {
final query = entry.key;
final minimumSimilarity = minimumSimilarityMap[query]!;
final textEmbedding = entry.value;
final (potentialFileIDs, distances) =
await searchClosestVectors(textEmbedding, 1000);
final queryResults = <QueryResult>[];
for (var i = 0; i < potentialFileIDs.length; i++) {
final similarity = 1 - distances[i];
if (similarity >= minimumSimilarity) {
queryResults
.add(QueryResult(potentialFileIDs[i].toInt(), similarity));
} else {
break;
}
}
queryToResults[query] = queryResults;
}
return queryToResults;
} catch (e, s) {
_logger.severe(
"Could not bulk find embeddings similarities using vector DB",
e,
s,
);
rethrow;
}
}
}
class VectorDbStats {
final int size;
final int capacity;
final int dimensions;
// in bytes
final int fileSize;
final int memoryUsage;
final int expansionAdd;
final int expansionSearch;
VectorDbStats({
required this.size,
required this.capacity,
required this.dimensions,
required this.fileSize,
required this.memoryUsage,
required this.expansionAdd,
required this.expansionSearch,
});
@override
String toString() {
return "VectorDbStats(size: $size, capacity: $capacity, dimensions: $dimensions, file size on disk (bytes): $fileSize, memory usage (bytes): $memoryUsage, expansionAdd: $expansionAdd, expansionSearch: $expansionSearch)";
}
}

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import "dart:io" show File;
import "dart:math";
import "package:collection/collection.dart";
@@ -9,6 +10,7 @@ import 'package:path_provider/path_provider.dart';
import "package:photos/core/event_bus.dart";
import "package:photos/db/common/base.dart";
import "package:photos/db/ml/base.dart";
import "package:photos/db/ml/clip_vector_db.dart";
import "package:photos/db/ml/db_model_mappers.dart";
import 'package:photos/db/ml/schema.dart';
import "package:photos/events/embedding_updated_event.dart";
@@ -18,6 +20,7 @@ import "package:photos/models/ml/face/face.dart";
import "package:photos/models/ml/face/face_with_embedding.dart";
import "package:photos/models/ml/ml_versions.dart";
import "package:photos/models/ml/vector.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart";
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
import "package:photos/services/machine_learning/ml_result.dart";
@@ -84,6 +87,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
"MLDataDB Migration took ${stopwatch.elapsedMilliseconds} ms",
);
stopwatch.stop();
_logger.info("Starting CLIP vector DB migration check unawaited");
if (flagService.enableVectorDb) unawaited(checkMigrateFillClipVectorDB());
return asyncDBConnection;
}
@@ -103,9 +108,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
const String sql = '''
INSERT INTO $facesTable (
$fileIDColumn, $faceIDColumn, $faceDetectionColumn, $embeddingColumn, $faceScore, $faceBlur, $isSideways, $imageHeight, $imageWidth, $mlVersionColumn
$fileIDColumn, $faceIDColumn, $faceDetectionColumn, $embeddingColumn, $faceScore, $faceBlur, $isSideways, $imageHeight, $imageWidth, $mlVersionColumn
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT($fileIDColumn, $faceIDColumn) DO UPDATE SET $faceIDColumn = excluded.$faceIDColumn, $faceDetectionColumn = excluded.$faceDetectionColumn, $embeddingColumn = excluded.$embeddingColumn, $faceScore = excluded.$faceScore, $faceBlur = excluded.$faceBlur, $isSideways = excluded.$isSideways, $imageHeight = excluded.$imageHeight, $imageWidth = excluded.$imageWidth, $mlVersionColumn = excluded.$mlVersionColumn
ON CONFLICT($fileIDColumn, $faceIDColumn) DO UPDATE SET $faceIDColumn = excluded.$faceIDColumn, $faceDetectionColumn = excluded.$faceDetectionColumn, $embeddingColumn = excluded.$embeddingColumn, $faceScore = excluded.$faceScore, $faceBlur = excluded.$faceBlur, $isSideways = excluded.$isSideways, $imageHeight = excluded.$imageHeight, $imageWidth = excluded.$imageWidth, $mlVersionColumn = excluded.$mlVersionColumn
''';
final parameterSets = batch.map((face) {
final map = mapRemoteToFaceDB(face);
@@ -158,7 +163,7 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final db = await instance.asyncDB;
final String query = '''
SELECT $fileIDColumn, $mlVersionColumn
FROM $facesTable
FROM $facesTable
WHERE $mlVersionColumn >= $minimumMlVersion
''';
final List<Map<String, dynamic>> maps = await db.getAll(query);
@@ -212,21 +217,6 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
return ignoredClusterIDs.union(rejectClusterIDs);
}
@override
Future<Map<String, Set<String>>> getPersonToRejectedSuggestions() async {
final db = await instance.asyncDB;
final List<Map<String, dynamic>> rejectMaps = await db.getAll(
'SELECT $personIdColumn, $clusterIDColumn FROM $notPersonFeedback',
);
final Map<String, Set<String>> result = {};
for (final map in rejectMaps) {
final personID = map[personIdColumn] as String;
final clusterID = map[clusterIDColumn] as String;
result.putIfAbsent(personID, () => {}).add(clusterID);
}
return result;
}
@override
Future<Set<String>> getPersonClusterIDs(String personID) async {
final db = await instance.asyncDB;
@@ -282,9 +272,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final Map<String, List<Uint8List>> result = {};
final selectQuery = '''
SELECT fc.$clusterIDColumn, fe.$embeddingColumn
FROM $faceClustersTable fc
INNER JOIN $facesTable fe ON fc.$faceIDColumn = fe.$faceIDColumn
SELECT fc.$clusterIDColumn, fe.$embeddingColumn
FROM $faceClustersTable fc
INNER JOIN $facesTable fe ON fc.$faceIDColumn = fe.$faceIDColumn
WHERE fc.$clusterIDColumn IN (${List.filled(clusterIDs.length, '?').join(',')})
${limit != null ? 'LIMIT ?' : ''}
''';
@@ -338,10 +328,10 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final List<Map<String, dynamic>> faceMaps = await db.getAll(
'''
SELECT * FROM $facesTable
SELECT * FROM $facesTable
WHERE $faceIDColumn IN (
SELECT $faceIDColumn
FROM $faceClustersTable
SELECT $faceIDColumn
FROM $faceClustersTable
WHERE $clusterIDColumn IN (${List.filled(clusterIDs.length, '?').join(',')})
)
AND $fileIDColumn IN (${List.filled(fileId.length, '?').join(',')})
@@ -435,8 +425,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final List<Map<String, dynamic>> maps = await db.getAll(
'''
SELECT $clusterIDColumn, $faceIDColumn
FROM $faceClustersTable
SELECT $clusterIDColumn, $faceIDColumn
FROM $faceClustersTable
WHERE $clusterIDColumn IN (${List.filled(clusterIDs.length, '?').join(',')})
''',
[...clusterIDs],
@@ -527,21 +517,6 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
return result;
}
@override
Future<Map<String, Set<String>>> getPersonToClusterIDs() async {
final db = await instance.asyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT $personIdColumn, $clusterIDColumn FROM $clusterPersonTable',
);
final Map<String, Set<String>> result = {};
for (final map in maps) {
final personID = map[personIdColumn] as String;
final clusterID = map[clusterIDColumn] as String;
result.putIfAbsent(personID, () => {}).add(clusterID);
}
return result;
}
Future<Map<String, String>> getFaceIdToPersonIdForFaces(
Iterable<String> faceIDs,
) async {
@@ -611,9 +586,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
Future<Iterable<double>> getBlurValuesForCluster(String clusterID) async {
final db = await instance.asyncDB;
const String query = '''
SELECT $facesTable.$faceBlur
FROM $facesTable
JOIN $faceClustersTable ON $facesTable.$faceIDColumn = $faceClustersTable.$faceIDColumn
SELECT $facesTable.$faceBlur
FROM $facesTable
JOIN $faceClustersTable ON $facesTable.$faceIDColumn = $faceClustersTable.$faceIDColumn
WHERE $faceClustersTable.$clusterIDColumn = ?
''';
// const String query2 = '''
@@ -787,11 +762,11 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
while (true) {
// Query a batch of rows
final String query = '''
SELECT $faceIDColumn, $embeddingColumn
FROM $facesTable
WHERE $faceIDColumn IN (${faceIDs.map((id) => "'$id'").join(",")})
ORDER BY $faceIDColumn DESC
LIMIT $batchSize OFFSET $offset
SELECT $faceIDColumn, $embeddingColumn
FROM $facesTable
WHERE $faceIDColumn IN (${faceIDs.map((id) => "'$id'").join(",")})
ORDER BY $faceIDColumn DESC
LIMIT $batchSize OFFSET $offset
''';
final List<Map<String, dynamic>> maps = await db.getAll(query);
// Break the loop if no more rows
@@ -1015,8 +990,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
return db.then((db) async {
final List<Map<String, dynamic>> maps = await db.getAll(
'''
SELECT $clusterIDColumn, $faceIDColumn
FROM $faceClustersTable
SELECT $clusterIDColumn, $faceIDColumn
FROM $faceClustersTable
WHERE $clusterIDColumn IN (${List.filled(clusterIDs.length, '?').join(',')})
''',
[...clusterIDs],
@@ -1039,7 +1014,7 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final db = await instance.asyncDB;
const String sql = '''
INSERT INTO $clusterSummaryTable ($clusterIDColumn, $avgColumn, $countColumn) VALUES (?, ?, ?) ON CONFLICT($clusterIDColumn) DO UPDATE SET $avgColumn = excluded.$avgColumn, $countColumn = excluded.$countColumn
INSERT INTO $clusterSummaryTable ($clusterIDColumn, $avgColumn, $countColumn) VALUES (?, ?, ?) ON CONFLICT($clusterIDColumn) DO UPDATE SET $avgColumn = excluded.$avgColumn, $countColumn = excluded.$countColumn
''';
final List<List<Object?>> parameterSets = [];
int batchCounter = 0;
@@ -1188,7 +1163,7 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final result = await db.getAll(
'''
SELECT DISTINCT $facesTable.$fileIDColumn
FROM $faceClustersTable
FROM $faceClustersTable
JOIN $facesTable ON $faceClustersTable.$faceIDColumn = $facesTable.$faceIDColumn
WHERE $faceClustersTable.$clusterIDColumn = ?
''',
@@ -1219,8 +1194,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
final notInParam = exceptClusters?.map((e) => "'$e'").join(',') ?? '';
final db = await instance.asyncDB;
final result = await db.getAll('''
SELECT DISTINCT $facesTable.$fileIDColumn
FROM $facesTable
SELECT DISTINCT $facesTable.$fileIDColumn
FROM $facesTable
JOIN $faceClustersTable on $faceClustersTable.$faceIDColumn = $facesTable.$faceIDColumn
WHERE $faceClustersTable.$clusterIDColumn NOT IN ($notInParam);
''');
@@ -1249,6 +1224,121 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
return embeddings;
}
Future<void> checkMigrateFillClipVectorDB({bool force = false}) async {
_logger.info("Waiting for ClipVectorDB to be ready");
await Future.delayed(const Duration(milliseconds: 100));
_logger.info("Checking if ClipVectorDB migration is needed");
// Check if vector DB migration has run
_logger.info("Checking if ClipVectorDB migration has run");
final documentsDirectory = await getApplicationDocumentsDirectory();
final migrationFlagFile =
File(join(documentsDirectory.path, 'clip_vector_migration_done'));
if (await migrationFlagFile.exists() && !force) {
_logger.info("ClipVectorDB migration not needed, already done");
return;
}
// Get total count first to track progress
_logger.info("Getting total count of clip embeddings");
final db = await instance.asyncDB;
final countResult =
await db.getAll('SELECT COUNT($fileIDColumn) as total FROM $clipTable');
final totalCount = countResult.first['total'] as int;
if (totalCount == 0) {
_logger.info("No clip embeddings to migrate");
await migrationFlagFile.create();
return;
}
_logger.info("Total count of clip embeddings: $totalCount");
_logger.info("First time referencing ClipVectorDB in migration");
final clipVectorDB = ClipVectorDB.instance;
_logger.info("ClipVectorDB referenced");
await clipVectorDB.deleteAllEmbeddings();
_logger.info("ClipVectorDB all embeddings cleared");
_logger
.info("Starting migration of $totalCount clip embeddings to vector DB");
const batchSize = 1000;
int offset = 0;
int processedCount = 0;
int weirdCount = 0;
int whileCount = 0;
final stopwatch = Stopwatch()..start();
try {
while (true) {
whileCount++;
_logger.info("$whileCount st round of while loop");
// Allow some time for any GC to finish
await Future.delayed(const Duration(milliseconds: 100));
_logger.info("Reading $batchSize rows from DB");
final List<Map<String, dynamic>> results = await db.getAll('''
SELECT $fileIDColumn, $embeddingColumn
FROM $clipTable
ORDER BY $fileIDColumn DESC
LIMIT $batchSize OFFSET $offset
''');
_logger.info("Got ${results.length} results from DB");
if (results.isEmpty) {
_logger.info("No more results, breaking out of while loop");
break;
}
_logger.info("Processing ${results.length} results");
final List<int> fileIDs = [];
final List<Float32List> embeddings = [];
for (final result in results) {
final embedding =
Float32List.view((result[embeddingColumn] as Uint8List).buffer);
if (embedding.length == 512) {
fileIDs.add(result[fileIDColumn] as int);
embeddings.add(Float32List.view(result[embeddingColumn].buffer));
} else {
weirdCount++;
}
}
_logger.info(
"Got ${fileIDs.length} valid embeddings, $weirdCount weird embeddings",
);
await ClipVectorDB.instance
.bulkInsertEmbeddings(fileIDs: fileIDs, embeddings: embeddings);
_logger.info("Inserted ${fileIDs.length} embeddings to ClipVectorDB");
processedCount += fileIDs.length;
offset += batchSize;
_logger.info(
"migrated $processedCount/$totalCount embeddings to ClipVectorDB",
);
if (processedCount >= totalCount) {
_logger.info("All embeddings migrated, breaking out of while loop");
break;
}
_logger.info("Clearing out embeddings and fileIDs");
embeddings.clear();
fileIDs.clear();
results.clear();
// Allow some time for any GC to finish
_logger.info("Waiting for 100ms for GC to finish");
await Future.delayed(const Duration(milliseconds: 100));
}
_logger.info(
"migrated all $totalCount embeddings to ClipVectorDB in ${stopwatch.elapsed.inMilliseconds} ms, with $weirdCount weird embeddings not migrated",
);
await migrationFlagFile.create();
_logger.info("ClipVectorDB migration done, flag file created");
} catch (e) {
_logger.severe(
"Error migrating ClipVectorDB after ${stopwatch.elapsed.inMilliseconds} ms, clearing out DB again",
e,
);
await clipVectorDB.deleteAllEmbeddings();
rethrow;
} finally {
stopwatch.stop();
}
}
// Get indexed FileIDs
@override
Future<Map<int, int>> clipIndexedFileWithVersion() async {
@@ -1282,12 +1372,25 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) VALUES (?, ?, ?)',
_getRowFromEmbedding(embeddings.first),
);
if (flagService.enableVectorDb) {
await ClipVectorDB.instance.insertEmbedding(
fileID: embeddings.first.fileID,
embedding: embeddings.first.embedding,
);
}
} else {
final inputs = embeddings.map((e) => _getRowFromEmbedding(e)).toList();
await db.executeBatch(
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) values(?, ?, ?)',
inputs,
);
if (flagService.enableVectorDb) {
await ClipVectorDB.instance.bulkInsertEmbeddings(
fileIDs: embeddings.map((e) => e.fileID).toList(),
embeddings:
embeddings.map((e) => Float32List.fromList(e.embedding)).toList(),
);
}
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}
@@ -1298,6 +1401,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
await db.execute(
'DELETE FROM $clipTable WHERE $fileIDColumn IN (${fileIDs.join(", ")})',
);
if (flagService.enableVectorDb) {
await ClipVectorDB.instance.deleteEmbeddings(fileIDs);
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}
@@ -1305,6 +1411,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
Future<void> deleteClipIndexes() async {
final db = await instance.asyncDB;
await db.execute('DELETE FROM $clipTable');
if (flagService.enableVectorDb) {
await ClipVectorDB.instance.deleteAllEmbeddings();
}
Bus.instance.fire(EmbeddingUpdatedEvent());
}

View File

@@ -1,7 +0,0 @@
import "package:photos/events/event.dart";
class ComputeControlEvent extends Event {
final bool shouldRun;
ComputeControlEvent(this.shouldRun);
}

View File

@@ -1,8 +0,0 @@
import "package:photos/events/event.dart";
import "package:photos/models/collection/collection.dart";
class CreateNewAlbumEvent extends Event {
final Collection collection;
CreateNewAlbumEvent(this.collection);
}

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