Compare commits

...

339 Commits

Author SHA1 Message Date
Neeraj Gupta
6d0f44b1da [auth] Minor improvements (#3443)
## Description

## Tests
2024-09-24 16:51:41 +05:30
Neeraj Gupta
c656a1c6a4 Merge remote-tracking branch 'origin/main' into auth_window_size 2024-09-24 16:49:10 +05:30
Neeraj Gupta
14a3f426ce [AUTH] Replace Icon for bybit.com (#3279)
### Replaced old very transparented ByBit Icon with a Visible and Clear
Icon for in-App

### A new Original Background and Proper Text Color has been Added to
the Icon to Replace the Old One
2024-09-24 16:48:48 +05:30
Neeraj Gupta
cfe83a40e5 [auth] Added and updated icons (#3419)
## Description

Added logos:
- 23andMe
- ForUsAll
- Guideline
- Gusto
- ID.me
- Login.gov
- Titan

Updated:
- Canva
- Coinbase
- eBay

Fixed T-Mobile that had an icon but was removed from the json.
2024-09-24 16:47:31 +05:30
Neeraj Gupta
c90feccfa4 [auth] Bump version v4.0.1 2024-09-24 16:46:43 +05:30
Neeraj Gupta
06a55bc11a [auth] Use SelectableText for notes 2024-09-24 16:46:25 +05:30
Neeraj Gupta
aa8d910a0b [auth] Fix bottom bar for iPad 2024-09-24 16:40:09 +05:30
Neeraj Gupta
75362199e5 [server] Remove invites & remove members during closeFamily (#3442)
## Description

Currently, we force the admin to remove the members themself and revoke
the invite before a family account can be closed.
Going forward, this action will be automatically done. This will help in
reducing support overhead.

## Tests
2024-09-24 16:17:49 +05:30
Neeraj Gupta
006eed1cd8 [auth] Request authentication to view passkeys (#3424)
## Description

## Tests
2024-09-24 16:01:31 +05:30
Neeraj Gupta
9dd18f0137 [auth] Show notes on bottom action bar 2024-09-24 15:59:51 +05:30
Neeraj Gupta
40f34417d9 [auth] Fix state refresh bug 2024-09-24 15:50:42 +05:30
Neeraj Gupta
8c722c39ec [server] Remove invites & remove members during closeFamily 2024-09-24 15:28:47 +05:30
Manav Rathi
4e6dd14c71 [web] People bar - Part x/x (#3440) 2024-09-24 13:55:19 +05:30
Manav Rathi
34edc9b2e8 new 2024-09-24 12:46:11 +05:30
Manav Rathi
51f64799aa rename 2024-09-24 12:25:57 +05:30
Manav Rathi
4791b10d91 rename 2024-09-24 12:21:16 +05:30
Manav Rathi
c595c88e82 person sugg 2024-09-24 12:12:34 +05:30
Manav Rathi
6657695858 Fix 2024-09-24 12:06:37 +05:30
Manav Rathi
bc5be62de4 Header 2024-09-24 11:48:44 +05:30
Manav Rathi
78fd9cc6e6 Select 2024-09-24 11:35:30 +05:30
Manav Rathi
29aa608399 Ah 2024-09-24 10:19:40 +05:30
Manav Rathi
310e319057 2nd dup 2024-09-24 10:15:22 +05:30
Manav Rathi
bef3d0949c Mode switcher 2024-09-24 10:04:19 +05:30
Manav Rathi
e45baf6238 Button 2024-09-24 09:48:47 +05:30
Manav Rathi
454f93fadb rename 2024-09-24 09:35:19 +05:30
Manav Rathi
76a6b7402c [web] New translations (#3433)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-24 08:56:16 +05:30
Manav Rathi
9d4c5b29b3 [web] Gallery bar refactoring (#3437)
Prepping for showing people here
2024-09-24 08:55:53 +05:30
Manav Rathi
ae50c83960 Fix 2024-09-24 08:35:50 +05:30
Manav Rathi
c6fdd6352e Add an exception 2024-09-24 08:33:27 +05:30
Manav Rathi
bed57c083c Fix lints 2024-09-24 08:29:59 +05:30
Manav Rathi
cda5caec3f Move 2024-09-24 08:13:15 +05:30
Manav Rathi
01d48b0fcc Rearrange 2024-09-24 08:09:25 +05:30
Manav Rathi
4780f545fe Rearrange 2024-09-24 08:02:56 +05:30
Manav Rathi
80ed1e47b0 Move 2024-09-24 07:58:48 +05:30
Manav Rathi
5c7f775f14 Move 2024-09-24 07:53:21 +05:30
Manav Rathi
7ff5d40152 wip: checkpoint 2024-09-24 07:14:21 +05:30
Manav Rathi
b4dbd942ab wip: checkpoint 2024-09-24 07:03:05 +05:30
Manav Rathi
627a53428a wip: checkpoint 2024-09-24 06:48:46 +05:30
Manav Rathi
35581099fc New order 2024-09-24 05:35:09 +05:30
Manav Rathi
e62dfe0a07 Unenum 2024-09-24 04:59:29 +05:30
Manav Rathi
21ae075674 Move 2024-09-24 04:51:06 +05:30
Manav Rathi
2660cee263 Rename 2024-09-24 04:49:08 +05:30
Manav Rathi
2ae8a11138 Rename 2024-09-24 04:48:09 +05:30
Crowdin Bot
70c62a62a6 New Crowdin translations by GitHub Action 2024-09-23 23:10:26 +00:00
Manav Rathi
a43599aad2 [web] Empty state tweaks (#3432)
- Stay on empty albums after ops
- Modify empty state message
2024-09-24 04:39:35 +05:30
Manav Rathi
5bcce4d610 Modify empty state message 2024-09-24 04:34:03 +05:30
Manav Rathi
d61d9b95ad Stay on empty albums
Change the behaviour to stay on the empty album (instead of automatically
jumping to all) even after all files have been deleted/moved from it. Use case
is that I might want to upload new ones.
2024-09-24 04:33:14 +05:30
Connor McCurdy
682d97f15a Delete auth/assets/custom-icons/icons/drop.svg
Removed drop logo that was added in initial commit
2024-09-23 11:04:14 -05:00
Connor McCurdy
46b210c600 Removed drop
Realized it's a dead platform
2024-09-23 10:59:47 -05:00
Manav Rathi
4f5f90259d [web] More refactoring, prepping for the people bar (#3430) 2024-09-23 20:53:18 +05:30
Manav Rathi
d8d5e62888 deprecate half 2024-09-23 20:40:06 +05:30
Manav Rathi
2cef85dfd9 new 2024-09-23 20:36:07 +05:30
Manav Rathi
31ef1e4e29 Genericize 2024-09-23 20:24:02 +05:30
Manav Rathi
952e9254b4 rearrange 2024-09-23 20:06:37 +05:30
Manav Rathi
8e7ec58f45 rearrange 2024-09-23 19:58:45 +05:30
Manav Rathi
7422568093 rearrange 2024-09-23 19:48:29 +05:30
Manav Rathi
39b09abc50 person 2024-09-23 19:35:20 +05:30
Manav Rathi
8b3315e85f Cleanup 2024-09-23 19:26:08 +05:30
Manav Rathi
19273927d1 wip: checkpoint 2024-09-23 19:17:07 +05:30
Manav Rathi
374625f5b3 Use a callback ref so that it's always updated
https://legacy.reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
2024-09-23 19:03:13 +05:30
Manav Rathi
2fae026a77 Make sure types are at the latest 2024-09-23 19:02:23 +05:30
Manav Rathi
01488be836 wip: checkpoint 2024-09-23 17:28:11 +05:30
Manav Rathi
953c915508 Tweak 2024-09-23 17:20:53 +05:30
Manav Rathi
9a0ea3ac43 Match the event name 2024-09-23 16:59:39 +05:30
Manav Rathi
42d36b35d2 [web] Exif fixes and improvements (#3426) 2024-09-23 16:18:18 +05:30
Manav Rathi
630b6d4101 Fail the upload of file instead of adding malformed metadata
Mobile app crash

 [DiffFetcher] [SEVERE] [2024-09-14 01:03:33.632159] type 'List<dynamic>' is not a subtype of type 'int?'
⤷ type: _StackTrace
⤷ error: #0      PubMagicMetadata.fromMap (package:photos/models/metadata/file_magic.dart:91)

ce5354e19d/mobile/lib/models/metadata/file_magic.dart (L91)
2024-09-23 15:44:23 +05:30
Manav Rathi
72ba8bcd64 Fix crash on viewing exif with the following data:
{
    "tags": {
        "icc": {
            "value": {
                "en-US": "Camera RGB Profile",
                ...
                "zh-CN": "?? RGB ????"
            },
            "description": {
                "en-US": "Camera RGB Profile",
                "es-ES": "Perfil RGB para CÔøΩmara",
                ...
                "zh-CN": "?? RGB ????"
            }
        }
    }
}
2024-09-23 15:30:58 +05:30
Manav Rathi
93f753fdff [web] Live photo clubbing improvements (#3422) 2024-09-23 15:12:24 +05:30
Aman Raj Singh Mourya
27defa92c2 [auth] extract strings 2024-09-23 15:12:15 +05:30
Aman Raj Singh Mourya
6ba9ed9f8d [auth] authentication to view passkey 2024-09-23 15:11:20 +05:30
Manav Rathi
6c5c3131f5 Handle tz 2024-09-23 14:39:34 +05:30
Neeraj Gupta
bb23c750a5 [mob] Discover improvements (#3421)
## Description

## Tests
2024-09-23 14:38:16 +05:30
Neeraj Gupta
c036c8c7ba [mob] Bump version 2024-09-23 14:34:35 +05:30
Neeraj Gupta
6464bf172f refactor 2024-09-23 14:30:40 +05:30
Neeraj Gupta
46e6af660e [mob] Log to match count for magicSearch 2024-09-23 14:29:02 +05:30
Neeraj Gupta
b18d987ba2 [mob] Upgrade discover results more frequently 2024-09-23 14:26:31 +05:30
Manav Rathi
6b12f0a595 Fix 2024-09-23 13:59:48 +05:30
connor
40b2b725b4 [auth] Added and updated icons 2024-09-23 03:20:45 -05:00
Manav Rathi
00346db9f9 Add GT special case 2024-09-23 13:45:33 +05:30
Manav Rathi
78826d7782 Keep the JSON avail in the top leevl fn 2024-09-23 13:40:04 +05:30
Manav Rathi
31f591c28f Move 2024-09-23 13:35:18 +05:30
Manav Rathi
0b0d8bd026 Rename 2024-09-23 13:30:39 +05:30
Manav Rathi
76e4d535b4 [desktop] People bar - WIP - Part x/x (#3418) 2024-09-23 13:11:15 +05:30
Neeraj Gupta
7f00b2619e [auth] New translations (#3414)
New translations from
[Crowdin](https://crowdin.com/project/ente-authenticator-app)
2024-09-23 11:54:58 +05:30
Manav Rathi
6a592af94a tweak 2024-09-23 11:50:54 +05:30
Neeraj Gupta
bea6e8b473 [auth] Make compact mode default & minor fixes (#3416)
## Description

## Tests
2024-09-23 11:48:56 +05:30
Neeraj Gupta
7cfdd6ec55 [mob] Safe parse h/w from public magicMetadata (#3417)
## Description

## Tests
2024-09-23 11:48:31 +05:30
Manav Rathi
b97cf93c12 wip: checkpoint 2024-09-23 11:48:25 +05:30
Manav Rathi
2865113c4d wip: checkpoint 2024-09-23 11:42:49 +05:30
Neeraj Gupta
ee1dbd7e84 Safe parse h/w from public magicMetadata 2024-09-23 11:42:15 +05:30
Neeraj Gupta
5a21c932df Lint fix 2024-09-23 11:29:45 +05:30
Manav Rathi
c4021a82f5 Help out tsc
Ref: https://stackoverflow.com/questions/45555748/assign-to-union-type-in-typescript
2024-09-23 11:29:04 +05:30
Neeraj Gupta
6e209a68e2 [mobile] New translations (#3413)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-app)
2024-09-23 11:27:18 +05:30
Neeraj Gupta
090e2f235b Update auth faq.md (#3415)
## Description

## Tests
2024-09-23 11:26:04 +05:30
Neeraj Gupta
a536d6af63 auth: show action button to update windows cert 2024-09-23 11:25:36 +05:30
Manav Rathi
53cf029f00 wip: checkpoint 2024-09-23 11:20:31 +05:30
Neeraj Gupta
48d6c2d008 [auth] Use web for FAQ 2024-09-23 11:12:52 +05:30
Manav Rathi
28095ca935 Extract 2024-09-23 11:04:00 +05:30
Manav Rathi
d31d7592cb Use standard memo
Too many renders for this component (both before and after):
- page load - 23
- click - 6
2024-09-23 10:57:12 +05:30
Neeraj Gupta
7645ec0e24 Update auth faq.md 2024-09-23 10:38:38 +05:30
Neeraj Gupta
5c0fe9c411 [auth] Use help website for FAQ 2024-09-23 10:31:31 +05:30
Manav Rathi
80416d5b90 tweak 2024-09-23 10:11:20 +05:30
Manav Rathi
9ce432681e Reorder 2024-09-23 10:08:29 +05:30
Manav Rathi
e91cddbc25 Restore clickable header 2024-09-23 09:58:38 +05:30
Neeraj Gupta
a37dcceb41 [auth] Enable compact mode by default 2024-09-23 09:44:11 +05:30
Manav Rathi
aa31e4354c [web] New translations (#3411)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-23 09:11:53 +05:30
Crowdin Bot
a92115ffb1 New Crowdin translations by GitHub Action 2024-09-23 01:17:18 +00:00
Crowdin Bot
7f0d028b55 New Crowdin translations by GitHub Action 2024-09-23 01:04:53 +00:00
Crowdin Bot
628edf713b New Crowdin translations by GitHub Action 2024-09-23 00:33:54 +00:00
Neeraj Gupta
4f74470abe [auth] Minor fixes (#3403)
## Description

## Tests
2024-09-22 22:32:22 +05:30
Neeraj Gupta
b5ad13ee69 [auth] Add device auth check for passkey 2024-09-22 22:29:07 +05:30
Neeraj Gupta
4383512540 [auth] Minor fix 2024-09-22 22:26:35 +05:30
Neeraj Gupta
59a68b56bc [auth] Improve account name parsing 2024-09-22 22:23:58 +05:30
Neeraj Gupta
09c4b19449 [auth][l10n]: Add Bulgarian translation (#3402)
## Description

## Tests
2024-09-22 22:17:49 +05:30
Crea7orX
d4cd71b56c [auth][l10n]: Add Bulgarian translation 2024-09-22 18:52:19 +03:00
Vishnu Mohandas
a01ea511e9 [docs] Update links to Auth (#3393) 2024-09-22 10:11:45 +05:30
vishnukvmd
ab6f514d23 Update links to Auth 2024-09-21 21:41:25 -07:00
Manav Rathi
b4d8dea2ef [desktop] Add CHANGELOG entry for shared export (#3385)
https://github.com/ente-io/ente/pull/3319
2024-09-21 18:22:19 +05:30
Manav Rathi
2abd0b0588 [desktop] Add CHANGELOG entry for shared export
https://github.com/ente-io/ente/pull/3319
2024-09-21 18:21:23 +05:30
Manav Rathi
dae0492800 [web] Album cast dialog - Non functional tweaks (#3384) 2024-09-21 18:16:44 +05:30
Trekky12
94a8ff2c6f Include exporting of shared files on desktop (#3319)
## Description
This PR adds a new switch on the ExportModal to include export of shared
files.
This should fix https://github.com/ente-io/ente/discussions/2322
2024-09-21 18:14:09 +05:30
Manav Rathi
aae00dcc15 Doc 2024-09-21 17:59:41 +05:30
Manav Rathi
18c7d59f90 Spacing 2024-09-21 17:57:29 +05:30
Manav Rathi
fcf87d237b Spacing 2024-09-21 17:39:28 +05:30
Manav Rathi
f88022730a Regular button works for us 2024-09-21 17:35:37 +05:30
Manav Rathi
753ed30d5c Tinker 2024-09-21 17:25:13 +05:30
Manav Rathi
216be38915 Use same convention for modals 2024-09-21 17:23:17 +05:30
Manav Rathi
164ace9f8c Tinker 2024-09-21 17:17:37 +05:30
Manav Rathi
a97bb195b6 Update docs
We're moving to nanoids
2024-09-21 17:09:39 +05:30
Manav Rathi
9ecb7c4044 Remove unused 2024-09-21 17:04:37 +05:30
Manav Rathi
1895e90b3e Workaround 2024-09-21 17:02:49 +05:30
Manav Rathi
f118a9d2f2 Rename to mirror sender 2024-09-21 16:49:15 +05:30
Manav Rathi
bb5261f73b Tweaks 2024-09-21 16:42:46 +05:30
Manav Rathi
225dade722 Use types 2024-09-21 16:36:08 +05:30
Manav Rathi
c39a3c789d Add types 2024-09-21 16:30:03 +05:30
Manav Rathi
28580cf107 Prune 2024-09-21 16:19:31 +05:30
Manav Rathi
6799ee3832 Prune unused 2024-09-21 16:15:09 +05:30
Manav Rathi
d86df11f15 [web] New translations (#3383)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-21 15:50:35 +05:30
Crowdin Bot
7b04bd548a New Crowdin translations by GitHub Action 2024-09-21 10:19:47 +00:00
Manav Rathi
ffaf4659ce [web] Remove unnecessary translation (#3382)
This title is not adding anything to the dialog
2024-09-21 15:49:02 +05:30
Manav Rathi
8fc38244f3 [web] Remove unnecessary translation
This title is not adding anything to the dialog
2024-09-21 15:48:15 +05:30
Manav Rathi
415cf451a8 [web] New translations (#3380)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2024-09-21 15:06:19 +05:30
Crowdin Bot
f4709d3442 New Crowdin translations by GitHub Action 2024-09-21 09:34:20 +00:00
Manav Rathi
0f6c8a6441 [web] Translation cleanup (#3379) 2024-09-21 14:52:01 +05:30
Manav Rathi
f337b1ff36 Move 2024-09-21 14:45:56 +05:30
Manav Rathi
2799652d3a Rename 2024-09-21 14:42:41 +05:30
Manav Rathi
6b92acbb11 Rename 2024-09-21 14:38:37 +05:30
Manav Rathi
a343fe5427 Rename 2024-09-21 14:35:42 +05:30
Manav Rathi
d10e37454d Rename 2024-09-21 14:31:54 +05:30
Manav Rathi
1cb80e619b Rename 2024-09-21 14:31:09 +05:30
Manav Rathi
d307b4bf07 Rename 2024-09-21 14:28:14 +05:30
Manav Rathi
5967f2a66a Rename 2024-09-21 14:25:46 +05:30
Manav Rathi
6e6248a3c2 Rename 2024-09-21 14:23:42 +05:30
Manav Rathi
aff37dc5df Rename 2024-09-21 14:21:21 +05:30
Manav Rathi
b2b4a703ac Rename 2024-09-21 14:17:53 +05:30
Manav Rathi
46f9a16db9 Rename 2024-09-21 14:14:09 +05:30
Manav Rathi
d232c94547 Move 2024-09-21 14:12:36 +05:30
Manav Rathi
dc6217eb7a Rename 2024-09-21 14:11:15 +05:30
Manav Rathi
98123438fa Move 2024-09-21 14:10:29 +05:30
Manav Rathi
4c51c960f2 Rename 2024-09-21 14:07:44 +05:30
Manav Rathi
a89d7e472f Rename 2024-09-21 14:02:53 +05:30
Manav Rathi
ef1b192ced Rename 2024-09-21 14:01:05 +05:30
Manav Rathi
19d82d332c Move 2024-09-21 14:00:14 +05:30
Manav Rathi
0d97355838 Rename 2024-09-21 13:58:57 +05:30
Manav Rathi
04b296587a Move 2024-09-21 13:52:57 +05:30
Manav Rathi
3193778c98 Rename 2024-09-21 13:52:21 +05:30
Manav Rathi
4a7ec88424 Rename 2024-09-21 13:49:56 +05:30
Manav Rathi
201bda3999 Rename 2024-09-21 13:48:24 +05:30
Manav Rathi
50ab94a355 Rename 2024-09-21 13:46:05 +05:30
Manav Rathi
790f34662a Move 2024-09-21 13:44:19 +05:30
Manav Rathi
d0138310dc Rename Rename 2024-09-21 13:43:07 +05:30
Manav Rathi
cb66494924 Rename 2024-09-21 13:33:33 +05:30
Manav Rathi
a322a28e44 Rename 2024-09-21 13:33:01 +05:30
Manav Rathi
a95a21790b Rename 2024-09-21 13:27:37 +05:30
Manav Rathi
e8d0673a9a [web] Collection bar refactoring (#3377)
Prep for showing people here.
2024-09-21 13:23:56 +05:30
Manav Rathi
f8f4db409b Fix lints 2024-09-21 13:18:30 +05:30
Manav Rathi
f4e260cfb3 Inline 2024-09-21 12:46:36 +05:30
Manav Rathi
e605169ac1 Height 2024-09-21 12:42:06 +05:30
Manav Rathi
811fe93dcc Wrap all network ops 2024-09-21 12:31:10 +05:30
Manav Rathi
6d7a6b86f7 Mark unawaited 2024-09-21 12:18:54 +05:30
Manav Rathi
5760b6a56b Reduce boilerplate 2024-09-21 12:16:27 +05:30
Manav Rathi
6b04ef69ed memo
preemptive optimization, might not be necessary
2024-09-21 11:54:05 +05:30
Manav Rathi
2f0d06cad7 Cleanup 2024-09-21 11:52:20 +05:30
Manav Rathi
08ee4e2861 Dedup 2024-09-21 11:49:18 +05:30
Manav Rathi
282b440d0f Cleanup 2024-09-21 11:46:39 +05:30
Manav Rathi
c9e29dbcbe Direct 2024-09-21 11:39:31 +05:30
Manav Rathi
ca62fb5105 Direct 2024-09-21 11:35:17 +05:30
Manav Rathi
58ca0a5cd4 Direct 2024-09-21 11:32:14 +05:30
Neeraj Gupta
3ef7185166 [doc] Update Referral.md (#3375)
## Description

## Tests
2024-09-21 11:26:08 +05:30
Manav Rathi
b92e9f4c6e Direct 2024-09-21 11:26:07 +05:30
Manav Rathi
2f2eb4e265 Reify 2024-09-21 11:23:49 +05:30
Manav Rathi
17aa385782 Reify 2024-09-21 11:22:59 +05:30
Neeraj Gupta
0ac6ea9af8 [doc] Update Referral.md 2024-09-21 11:14:49 +05:30
Manav Rathi
fce4c9869e Reify 2024-09-21 11:05:04 +05:30
Manav Rathi
1eca25b050 Reify 2024-09-21 11:01:13 +05:30
Manav Rathi
47a0ee749e wrap 2024-09-21 10:56:50 +05:30
Manav Rathi
1c86a69cd6 Use 2024-09-21 10:53:49 +05:30
Manav Rathi
ce8310c874 Reify 2024-09-21 10:50:08 +05:30
Manav Rathi
e607b4e3ed Reify 2024-09-21 10:30:37 +05:30
Manav Rathi
498a4dcd4e Reify 2024-09-21 10:28:53 +05:30
Manav Rathi
624726734f Reify 2024-09-21 10:25:46 +05:30
Neeraj Gupta
4b4350c107 [mob] Fix various issues from bug tracking tool (#3362)
## Description

## Tests
2024-09-21 10:22:47 +05:30
Manav Rathi
ecb785d75c Rename 2024-09-21 10:17:41 +05:30
Manav Rathi
2c1637c55c Reify 2024-09-21 10:16:17 +05:30
Neeraj Gupta
31f210da31 [mob] Fix 2024-09-21 10:10:59 +05:30
Manav Rathi
777f298ca3 Less noise 2024-09-21 09:43:06 +05:30
Neeraj Gupta
c5c86c484e Merge remote-tracking branch 'origin/main' into cleanup_config 2024-09-21 09:41:34 +05:30
Neeraj Gupta
e9b3e76b50 [mob] Bump version v0.9.43 2024-09-21 09:40:57 +05:30
Manav Rathi
15e31f1763 Unwrap a component that has no logic 2024-09-21 09:40:48 +05:30
Neeraj Gupta
fffebec025 [mob] Fix sub price order 2024-09-21 09:40:29 +05:30
Manav Rathi
35957b1f40 Position the spinner better 2024-09-21 09:39:17 +05:30
Manav Rathi
6ee7500011 Inline and reify 2024-09-21 09:32:08 +05:30
Neeraj Gupta
803195afdd [mob] Fix lint 2024-09-21 09:17:24 +05:30
Manav Rathi
2c23c3463e Reify 2024-09-21 09:15:35 +05:30
Manav Rathi
39c5a3f1bd Reify 2024-09-21 09:09:27 +05:30
Manav Rathi
c625593162 Fwd 2024-09-21 09:03:20 +05:30
Manav Rathi
5864648c24 Abstract 2024-09-21 08:59:26 +05:30
Manav Rathi
b605e41f9e Wrap 2024-09-21 08:50:49 +05:30
Manav Rathi
ffc082eec4 3 2024-09-21 08:44:02 +05:30
Manav Rathi
692e12979b Remove args from handler 2024-09-21 08:32:22 +05:30
Manav Rathi
9ff6a79ada Unex 2024-09-21 08:14:56 +05:30
Manav Rathi
530ae6c0be Up 2024-09-21 08:14:00 +05:30
Manav Rathi
1aab753046 Inline 2024-09-21 08:13:10 +05:30
Manav Rathi
3e131464e6 Pl 2024-09-21 08:11:52 +05:30
Manav Rathi
1aa940d410 Inline 2024-09-21 08:11:10 +05:30
Manav Rathi
33bc19978c Inline 2024-09-21 08:09:41 +05:30
Manav Rathi
4670c1d712 Inline 2024-09-21 08:08:24 +05:30
Manav Rathi
ff32e6852e Inline 2024-09-21 08:06:19 +05:30
Manav Rathi
b39e727e0a Move 2024-09-21 08:03:54 +05:30
Manav Rathi
2a1931157c Doc 2024-09-21 07:57:35 +05:30
Manav Rathi
15370fa731 Rename Fin 2024-09-21 07:54:17 +05:30
Manav Rathi
b65a7055c1 Rename 2 2024-09-21 07:28:33 +05:30
Manav Rathi
1a336769b9 Rename 1 2024-09-21 07:15:39 +05:30
Manav Rathi
aa6eebca27 Inline 2024-09-21 07:13:15 +05:30
Manav Rathi
c41ad8b1a9 Inline 2024-09-21 07:13:07 +05:30
Manav Rathi
9ca651f4b7 Inline 2024-09-21 07:11:44 +05:30
Manav Rathi
255302e3cd Inline 2024-09-21 07:10:38 +05:30
Manav Rathi
a4fa8e0deb Inline 2024-09-21 07:09:30 +05:30
Manav Rathi
ffa1d90ed8 Inline 2024-09-21 07:08:31 +05:30
Manav Rathi
ef6734195f Inline 2024-09-21 07:05:31 +05:30
Manav Rathi
50a0dc754b Inline 2024-09-21 07:02:51 +05:30
Neeraj Gupta
19eb1bdb22 [cli] Handle decryption for new libsodium wrapper (#3374)
## Description

## Tests
2024-09-21 06:51:49 +05:30
Neeraj Gupta
f11493842e [cli] Handle decryption for new libsodium wrapper 2024-09-21 06:50:33 +05:30
Manav Rathi
b7a8e33665 Tweak 2024-09-21 06:41:30 +05:30
Manav Rathi
abce21d819 Move 2024-09-21 06:17:12 +05:30
Manav Rathi
2db43536f7 Doc 2024-09-21 05:59:44 +05:30
Manav Rathi
ee685bcc5d Rearrange 2024-09-21 05:47:49 +05:30
Manav Rathi
6a489ad520 wip: checkpoint 2024-09-20 20:24:11 +05:30
Manav Rathi
3c61b49548 checkpoint 2024-09-20 18:49:29 +05:30
Manav Rathi
6a24528ed0 wip: checkpoint 2024-09-20 18:36:09 +05:30
Manav Rathi
ebc40d1b65 checkpoint 2024-09-20 18:11:08 +05:30
Manav Rathi
405c0c343f [desktop] Clustering WIP - Part x/x (#3364)
The sync scaffolding is mostly in place now.
2024-09-20 17:52:57 +05:30
Neeraj Gupta
f8b77f71b5 [mob] Add is mounted check 2024-09-20 17:47:45 +05:30
Manav Rathi
ed907c71f8 Let tsc know 2024-09-20 17:45:49 +05:30
Neeraj Gupta
94513e8c8e [mob] Compute relevant position for magic search 2024-09-20 17:38:01 +05:30
Neeraj Gupta
f3e98cff34 [mob] Fix handling of empty fileIDToPosMap 2024-09-20 17:31:39 +05:30
Neeraj Gupta
49e7c4baaf Add mount check 2024-09-20 17:24:37 +05:30
Ashil
29a72ac4a1 [auth] Add onPressed color for Custom Keyboard in App Lock (#3361)
## Description

## Tests
2024-09-20 17:23:58 +05:30
Manav Rathi
2722b50cc0 Sequence 2024-09-20 17:22:29 +05:30
Aman Raj Singh Mourya
36079aa2dc [auth] Add onPressed color for Custom Keyboard in App Lock 2024-09-20 17:20:46 +05:30
Aman Raj Singh Mourya
891af00454 [mob][photos] Add onPressed State Color for Custom Keyboard in App Lock (#3360)
## Description

## Tests
2024-09-20 17:03:16 +05:30
Aman Raj Singh Mourya
783c0c48ef [mob][photos] On press color for custom keyboard 2024-09-20 16:50:32 +05:30
Manav Rathi
ed1c9df007 Funnel the same way 2024-09-20 16:50:02 +05:30
Manav Rathi
f64c0dcc86 Move into worker for now 2024-09-20 16:13:49 +05:30
Manav Rathi
9d1332bff1 Prune 2024-09-20 16:04:22 +05:30
Manav Rathi
000fe87ebb Prune unused 2024-09-20 16:00:59 +05:30
Manav Rathi
6344a3c640 bona fide 2024-09-20 15:55:46 +05:30
Manav Rathi
18a0b18a13 Auto debug 2024-09-20 15:47:40 +05:30
Neeraj Gupta
d4cdfc8834 [mob] HotFix: Show discover section for all users (#3358)
## Description

## Tests
2024-09-20 15:37:34 +05:30
Neeraj Gupta
aa2b81ad7e [mob] HotFix: Show discover section for all users 2024-09-20 15:36:45 +05:30
Manav Rathi
df17b11573 Make the animation fit the page better 2024-09-20 15:33:01 +05:30
Manav Rathi
8ca3b80e94 Match the (temp) search placeholder message 2024-09-20 14:00:53 +05:30
Manav Rathi
345cc2f34f Fix the UI updates 2024-09-20 14:00:53 +05:30
Manav Rathi
c4f70c370e Integrate clustering progress into ML status 2024-09-20 14:00:53 +05:30
Manav Rathi
e8b692b5ad Prep for clustering updates 2024-09-20 14:00:53 +05:30
Manav Rathi
7b552a1ee3 count 2024-09-20 14:00:53 +05:30
Manav Rathi
5d6ac29d71 Remove no-longer used hdbscan code
We'll follow mobile's linear clustering.
2024-09-20 14:00:52 +05:30
Manav Rathi
8a031360c5 Remove the debug scaffolding 2024-09-20 14:00:52 +05:30
Vishnu Mohandas
c9fd0183e7 [doc] Document limitations (#3356) 2024-09-20 11:52:53 +05:30
vishnukvmd
6753f1e9f7 [doc] Document limitations 2024-09-19 23:11:29 -07:00
Manav Rathi
806098961b [web] Transcode videos if they are smaller than 100 MB (#3355)
Fixes: https://github.com/ente-io/ente/issues/2581
2024-09-20 11:29:56 +05:30
Manav Rathi
21e45e8138 [web] Transcode videos if they are smaller than 100 MB
Fixes: https://github.com/ente-io/ente/issues/2581
2024-09-20 11:04:21 +05:30
Vishnu Mohandas
1de1273391 Update export.md 2024-09-20 00:14:52 +05:30
Manav Rathi
8ea7481a98 [desktop] Cgroups WIP - Part x/x (#3353) 2024-09-19 20:55:29 +05:30
Manav Rathi
12da709445 lf 2024-09-19 20:50:16 +05:30
Manav Rathi
5c601ab2cc We no longer have a maxHeight 2024-09-19 20:45:24 +05:30
Manav Rathi
87bdab027e Snapshot 2024-09-19 20:45:24 +05:30
Manav Rathi
50f4878d0f Doc 2024-09-19 20:45:24 +05:30
Manav Rathi
523336d644 Unalias 2024-09-19 20:45:24 +05:30
Manav Rathi
4b7104bf4e Inline 2024-09-19 20:45:24 +05:30
Manav Rathi
6a8ca4c2cf Inline 2024-09-19 20:45:24 +05:30
Manav Rathi
2e6c7d29e4 Inline 2024-09-19 20:45:24 +05:30
Manav Rathi
b7f86b3e89 T 2024-09-19 20:45:24 +05:30
Manav Rathi
384b4d2c35 [infra] Copycat db - Include pg_restore (#3352) 2024-09-19 20:39:04 +05:30
Manav Rathi
e6d7d2298c Update 2024-09-19 20:38:27 +05:30
Manav Rathi
6139ed45cd Revert "Remove postgres dep not needed in production"
This reverts commit e695f2eccb.
2024-09-19 20:33:42 +05:30
Manav Rathi
6662f51a5f [deskop] People WIP - Part x/x (#3351) 2024-09-19 18:30:08 +05:30
Manav Rathi
1108fa9f79 fix npe 2024-09-19 18:26:38 +05:30
Manav Rathi
2b02ea7409 filter 2024-09-19 18:26:19 +05:30
Manav Rathi
cdca58eb3c Tweak 2024-09-19 18:07:02 +05:30
Manav Rathi
0381dee786 buttons 2024-09-19 17:56:26 +05:30
Manav Rathi
1c727131ad Use a button 2024-09-19 17:22:00 +05:30
Manav Rathi
944070eb23 The blur is needed 2024-09-19 17:19:29 +05:30
Manav Rathi
f2b86ff1e1 Propagate 2024-09-19 17:19:29 +05:30
Manav Rathi
a14160f799 Person mode - 1 2024-09-19 17:19:29 +05:30
Manav Rathi
dcca546e5a [meta] [infra] Rename workflow file to use same extension as the rest (#3350) 2024-09-19 17:15:26 +05:30
Manav Rathi
bb0bdf113e [meta] [infra] Rename workflow file to use same extension as the rest 2024-09-19 17:13:11 +05:30
Manav Rathi
a323c7b31b [infra] copycat-db: update deps (#3349) 2024-09-19 17:11:36 +05:30
Manav Rathi
2d46b70d8f Update to latest scw 2024-09-19 17:10:39 +05:30
Manav Rathi
e695f2eccb Remove postgres dep not needed in production 2024-09-19 17:07:40 +05:30
Manav Rathi
1942935c3c [web] Show the focus-visible state on delete autofocus (#3348) 2024-09-19 17:01:02 +05:30
Manav Rathi
cef85ddd9f Add outline offset to the focus-visible indicator 2024-09-19 16:56:09 +05:30
Manav Rathi
341ef58970 Fix focus visible on opening dialog 2024-09-19 16:53:00 +05:30
Neeraj Gupta
983cfe4482 [mob][photos] Remove trigger to send logs from grant permissions screen (#3345)
## Description

This was added to debug an issue. The issue is resolved, so removing it.
2024-09-19 15:36:55 +05:30
Neeraj Gupta
2ae23dfa3d [docs] Update auth export.md (#3347)
## Description

## Tests
2024-09-19 15:25:28 +05:30
Neeraj Gupta
b269fddac2 Update export.md 2024-09-19 15:24:21 +05:30
Manav Rathi
ca5be3518b [server] Postgres 12 => 15 in sample docker compose file (+ add migration guide) (#3342) 2024-09-19 15:13:13 +05:30
ashilkn
b85a90e5dd [mob][photos] Remove unused debouncer 2024-09-19 14:36:41 +05:30
ashilkn
a4c47ffbd4 [mob][photos] Remove trigger to send logs from grand permissions screen 2024-09-19 14:33:58 +05:30
Neeraj Gupta
4ee9815971 [server] Increase waittime on delete error (#3344)
## Description

## Tests
2024-09-19 11:56:28 +05:30
Neeraj Gupta
5f873a0f7b Increase waittime on error 2024-09-19 11:55:54 +05:30
Neeraj Gupta
d02da225f8 [server] Slow down crons (#3343)
## Description

## Tests
2024-09-19 11:50:22 +05:30
Neeraj Gupta
a8c7dd52ba [server] Slow down crons 2024-09-19 11:49:48 +05:30
Manav Rathi
84900159ae Fix typos 2024-09-19 10:34:07 +05:30
Manav Rathi
6ed0ad806e pg 15 2024-09-19 10:30:58 +05:30
Manav Rathi
c1b6458e2e [web/desktop] Make images on deduplicate selectable (#3333)
The duplicate images page has a image preselected but there is no
checkbox available.
When unselecting this image there is no possibility to do a new
selection.

This PR adds the same checkbox like on the gallery to select images on
the deduplication page.
2024-09-19 09:52:58 +05:30
Neeraj Gupta
a7cc96d994 Bump auth version (#3341)
## Description

## Tests
2024-09-19 07:00:23 +05:30
Neeraj Gupta
2a483edbe4 Bump auth version 2024-09-19 06:59:56 +05:30
Neeraj Gupta
428d786f10 [infra] Use JAVA 17 for auth release workflow (#3340)
## Description

## Tests
2024-09-19 06:59:05 +05:30
Neeraj Gupta
7d66b4c29f Use JAVA 17 for auth release workflow 2024-09-19 06:58:27 +05:30
Trekky12
53b7ea6203 Make images on deduplicate selectable
The duplicate images page has a image preselected but there is no
checkbox available.
When unselecting this image there is no possibility to do a new
selection.

This commit adds the same checkbox like on the gallery to select images
on the deduplication page.
2024-09-18 19:50:08 +02:00
zDqrK404
1e8b184ed0 Merge branch 'ente-io:main' into main 2024-09-17 10:55:12 +06:00
zDqrK404
daa9e01729 [AUTH] Add Icon for ByBit.com 2024-09-16 01:54:20 +06:00
zDqrK404
a6ab51727c Delete auth/assets/custom-icons/icons/bybit.svg 2024-09-16 01:53:43 +06:00
Manav Rathi
4fe7ec6257 Add hint on how to remove the temporary container 2024-09-05 09:43:49 +05:30
Manav Rathi
bdacd1058e 16 => 15 for now 2024-09-05 09:41:38 +05:30
Manav Rathi
7fb31eee0a Punctuation 2024-09-05 09:41:38 +05:30
Manav Rathi
f1adcd4573 Add macOS example 2024-09-05 09:41:37 +05:30
Manav Rathi
130b2757a9 [docs] Postgres 12 => 16 migration guide 2024-09-05 09:41:37 +05:30
223 changed files with 6788 additions and 6591 deletions

View File

@@ -45,6 +45,11 @@ jobs:
with:
submodules: recursive
- name: Setup JDK 17
uses: actions/setup-java@v1
with:
java-version: 17
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
uses: subosito/flutter-action@v2
with:

View File

@@ -70,7 +70,7 @@ want to give back, please check out Ente Photos or spread the word.
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
</div>

View File

@@ -12,7 +12,7 @@ multi-device sync.
### Android
This repository's [GitHub
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
releases](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
contains APKs, built straight from source. These builds keep themselves updated,
without relying on third party stores.
@@ -33,7 +33,7 @@ You can alternatively install the build from PlayStore or F-Droid.
### Desktop
You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v3)
You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v4)
a native desktop app from this repository's GitHub releases. The desktop app
works on Windows, Linux and macOS.

View File

@@ -3,6 +3,9 @@
{
"title": "1xBet"
},
{
"title": "23andme"
},
{
"title": "3Commas"
},
@@ -119,6 +122,9 @@
{
"title": "Bybit"
},
{
"title": "Canva"
},
{
"title": "Capacities"
},
@@ -139,6 +145,9 @@
{
"title": "Cloudflare"
},
{
"title": "Coinbase"
},
{
"title": "CoinDCX"
},
@@ -188,6 +197,9 @@
"title": "dus.net",
"slug": "dusnet"
},
{
"title": "eBay"
},
{
"title": "ecitizen kenya",
"slug": "ecitizen_kenya"
@@ -223,6 +235,9 @@
"title": "Firefox",
"slug": "mozilla"
},
{
"title": "ForUsAll"
},
{
"title": "G2A"
},
@@ -252,6 +267,12 @@
"Government Gateway"
]
},
{
"title": "Guideline"
},
{
"title": "Gusto"
},
{
"title": "Habbo"
},
@@ -274,6 +295,10 @@
{
"title": "IceDrive"
},
{
"title": "ID.me",
"slug": "IDme"
},
{
"title": "Infomaniak"
},
@@ -343,6 +368,10 @@
"Local Wordpress"
]
},
{
"title": "Login.gov",
"slug": "login_gov"
},
{
"title": "Marketplace.tf",
"slug": "marketplacedottf"
@@ -586,6 +615,12 @@
"title": "Synology DSM",
"slug": "synology_dsm"
},
{
"title": "T-Mobile",
"altNames": [
"T Mobile"
]
},
{
"title": "TCPShield"
},
@@ -607,6 +642,9 @@
"title": "Termius",
"hex": "858585"
},
{
"title": "Titan"
},
{
"title": "TorGuard"
},
@@ -698,4 +736,4 @@
]
}
]
}
}

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg"><g transform="translate(26.145 5.696)" fill="none" fill-rule="evenodd"><path d="M7.824 10.031a6 6 0 0 1 7.492 3.984L32.42 69.958a6 6 0 1 1-11.476 3.509L3.84 17.523a6 6 0 0 1 3.984-7.492Z" fill="#D91A62"/><rect fill="#7BC144" transform="rotate(26 20.845 34.313)" x="14.845" y="-.937" width="12" height="70.5" rx="6"/><path fill="#49A848" d="m18.689 25.047 5.144 16.826-7.713 15.815-5.144-16.827 7.713-15.814z"/></g></svg>

After

Width:  |  Height:  |  Size: 480 B

View File

@@ -0,0 +1,6 @@
<svg width="126" height="45" viewBox="0 0 126 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.76597 0.337891H4.59152C1.87715 0.337891 0.5 1.14359 0.5 2.73083V42.0032C0.5 43.5914 1.87715 44.3961 4.59152 44.3961H6.76597C9.4814 44.3961 10.8575 43.5904 10.8575 42.0032V2.73083C10.8585 1.14359 9.4814 0.337891 6.76597 0.337891Z" fill="white"/>
<path d="M45.1179 39.5856C45.1179 34.9006 48.2073 30.9351 52.4743 29.5706C52.9617 27.3867 53.2075 24.978 53.2117 22.3445C53.2117 8.14862 45.9908 0.330078 32.878 0.330078H18.8996C16.9468 0.330078 16.0371 1.23137 16.0371 3.16736V41.5216C16.0371 43.4576 16.9468 44.3589 18.8985 44.3589H32.877C37.8761 44.3589 42.0159 43.2191 45.2282 41.0268C45.1578 40.5499 45.1211 40.0678 45.1169 39.5856M32.8759 35.5298H26.4398V9.15916H32.8759C40.9287 9.15916 42.6199 16.3296 42.6199 22.3445C42.6199 28.3594 40.9287 35.5298 32.8759 35.5298Z" fill="white"/>
<path d="M55.7368 34.7764C58.4165 34.7764 60.5889 36.9298 60.5889 39.5864C60.5889 42.243 58.4165 44.3964 55.7368 44.3964C53.0571 44.3964 50.8848 42.243 50.8848 39.5864C50.8848 36.9298 53.0571 34.7764 55.7368 34.7764Z" fill="#2EA76D"/>
<path d="M124.735 32.8363C124.593 32.7754 124.441 32.7417 124.286 32.7365C123.623 32.7144 123.171 32.9728 122.772 33.6126C122.536 33.996 122.303 34.3857 122.072 34.7754C121.192 36.2618 120.283 37.7965 118.962 38.8974C117.168 40.3912 114.57 41.0897 112.342 40.6842C111.017 40.4437 110.071 39.493 109.51 38.7388C108.482 37.3522 107.981 35.4855 108.061 33.3342C111.341 33.06 121.224 31.6114 122.01 24.1847C122.163 22.7446 121.749 21.4399 120.814 20.4104C119.629 19.1058 117.736 18.3862 115.487 18.3862C108.895 18.3862 102.389 25.2215 101.564 33.0138C101.338 35.1515 101.607 37.1253 102.364 38.8848C101.884 39.3449 101.335 39.7283 100.74 40.0224C100.187 40.283 99.7092 40.3365 99.3584 40.1769C98.9245 39.9773 98.7481 39.4741 98.6787 39.0886C98.4193 37.6621 98.7544 36.0223 99.1514 34.3279C99.3846 33.3342 99.6777 32.2995 99.9351 31.3856C100.732 28.5672 101.557 25.6533 101.282 22.7088C101.032 20.0134 99.1231 18.3389 96.3016 18.3389C92.3455 18.3389 89.7404 21.1374 88.0544 23.5366C88.0187 21.7487 87.5891 20.4504 86.7466 19.5827C85.9451 18.757 84.7676 18.3379 83.2486 18.3379C79.3682 18.3379 76.7956 21.026 75.1139 23.379C75.1359 23.1626 75.159 22.942 75.1832 22.7214C75.2893 21.713 75.3366 20.2676 74.5246 19.3747C74.0414 18.8432 73.3144 18.5732 72.3638 18.5732C71.7104 18.5532 71.057 18.6037 70.4152 18.7255C70.4057 18.7266 69.5339 18.8957 69.2019 19.1982C68.621 19.7266 68.8059 20.4556 68.912 20.8716C68.9256 20.9241 68.9382 20.9724 68.9445 21.0134C69.0107 21.4746 69.0307 21.941 69.0055 22.4063C68.8836 25.2362 68.3542 28.0651 67.851 30.4591C67.5789 31.7491 67.2733 33.0611 66.9707 34.3563C66.2985 37.2303 65.6051 40.2 65.249 43.1875C65.1703 43.7684 65.5757 44.3031 66.1566 44.3818C66.2092 44.3892 66.2627 44.3923 66.3163 44.3913L66.5253 44.3934C68.6578 44.4218 70.7534 44.3346 71.2461 43.1286C71.7913 41.7988 72.0928 40.1811 72.3596 38.7503L72.4772 38.1159C73.1096 34.8258 73.6621 32.4686 74.7756 29.4381C75.3492 27.876 76.3975 26.4695 77.2452 25.4169C78.2715 24.1427 79.3787 22.8958 80.6277 22.8643C81.1445 22.838 81.4817 22.9904 81.7359 23.3255C82.9492 24.9306 81.4387 30.1103 80.7937 32.3226C80.654 32.8006 80.5363 33.205 80.4617 33.5012L80.0174 35.2103C79.363 37.702 78.6865 40.2777 78.3398 42.8839C78.3052 43.1507 78.2726 43.4186 78.2453 43.6896L78.2159 44.053L78.489 44.261C79.0394 44.6844 82.9702 44.0919 83.0017 44.0825C84.4514 43.5971 84.7665 42.2452 84.8705 41.8009C85.1405 40.6517 85.3789 39.4773 85.609 38.3428L85.6258 38.2587C86.0586 36.1211 86.5061 33.9098 87.1752 31.7764C88.482 27.625 90.2236 24.7929 92.3539 23.358C93.2647 22.7425 94.2007 22.6742 94.624 23.1941C95.3604 24.0912 94.8939 26.5199 94.6944 27.5567C94.4191 28.9958 94.0662 30.4591 93.7258 31.8751L93.708 31.9476C93.4895 32.8499 93.2731 33.7512 93.0714 34.6557C92.4485 37.4583 91.9369 40.9332 93.5284 42.9385C94.3383 43.9606 95.5914 44.4785 97.2533 44.4785C99.0159 44.4785 100.674 43.8955 102.471 42.6444C102.966 42.2967 103.466 41.8923 104.05 41.4111C106.073 43.6906 108.37 44.6676 111.687 44.6676C119.185 44.6676 122.71 39.7283 124.71 36.0129C124.976 35.537 125.211 35.0443 125.412 34.538C125.66 33.8542 125.362 33.1062 124.734 32.8373M115.539 23.4557C115.549 23.5713 115.559 23.6868 115.56 23.8034C115.56 23.9778 115.551 24.1522 115.533 24.3265C115.477 24.9043 115.363 25.4757 115.194 26.0314C114.124 29.4717 111.112 30.0809 108.506 30.1681C108.849 28.6996 109.358 27.2741 110.021 25.919C111.288 23.358 112.881 21.7676 114.175 21.7666C114.295 21.7666 114.414 21.7844 114.528 21.817C114.588 21.838 114.646 21.8632 114.701 21.8937L114.756 21.9178C114.778 21.9262 114.799 21.9368 114.819 21.9494C114.876 21.9872 114.928 22.0313 114.977 22.0786L115.021 22.1195C115.039 22.1342 115.054 22.15 115.07 22.1668C115.105 22.2078 115.137 22.2519 115.166 22.2981L115.204 22.3559C115.233 22.3958 115.259 22.4378 115.282 22.4809C115.296 22.5082 115.307 22.5387 115.342 22.6185C115.379 22.7036 115.41 22.7908 115.438 22.879L115.462 22.9862C115.491 23.0986 115.513 23.212 115.528 23.3265L115.539 23.4557Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 508 508" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><g transform="matrix(.26718 0 0 .26718 0 0)"><circle cx="950" cy="950" r="950" fill="#7d2ae7"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial1)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial2)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial3)"/><circle cx="950" cy="950" r="950" fill="url(#prefix___Radial4)"/></g><path d="M446.744 276.845c-.665 0-1.271.43-1.584 1.33-4.011 11.446-9.43 18.254-13.891 18.254-2.563 0-3.6-2.856-3.6-7.336 0-11.21 6.71-34.982 10.095-45.82.392-1.312.646-2.485.646-3.483 0-3.15-1.722-4.696-5.987-4.696-4.598 0-9.547 1.8-14.36 10.233-1.663-7.435-6.691-10.683-13.715-10.683-8.12 0-15.965 5.224-22.421 13.696-6.456 8.471-14.048 11.25-19.76 9.88 4.108-10.057 5.634-17.57 5.634-23.145 0-8.746-4.324-14.028-11.308-14.028-10.624 0-16.747 10.134-16.747 20.797 0 8.237 3.736 16.708 11.954 20.817-6.887 15.573-16.943 29.66-20.758 29.66-4.93 0-6.379-24.123-6.105-41.38.176-9.9.998-10.408.998-13.401 0-1.722-1.115-2.896-5.595-2.896-10.448 0-13.676 8.844-14.165 18.998a50.052 50.052 0 01-1.8 11.406c-4.363 15.573-13.363 27.39-19.232 27.39-2.72 0-3.463-2.72-3.463-6.28 0-11.21 6.28-25.219 6.28-37.173 0-8.784-3.854-14.34-11.112-14.34-8.55 0-19.858 10.173-30.56 29.229 3.521-14.595 4.97-28.721-5.459-28.721a14.115 14.115 0 00-6.476 1.683 3.689 3.689 0 00-2.113 3.56c.998 15.535-12.521 55.329-25.336 55.329-2.328 0-3.463-2.524-3.463-6.593 0-11.23 6.691-34.943 10.056-45.801.43-1.409.666-2.622.666-3.678 0-2.974-1.84-4.5-6.007-4.5-4.578 0-9.547 1.741-14.34 10.174-1.683-7.435-6.711-10.683-13.735-10.683-11.523 0-24.397 12.19-30.051 28.076-7.572 21.208-22.832 41.692-43.375 41.692-18.645 0-28.486-15.515-28.486-40.03 0-35.392 25.982-64.308 45.253-64.308 9.215 0 13.617 5.869 13.617 14.869 0 10.897-6.085 15.964-6.085 20.112 0 1.272 1.057 2.524 3.15 2.524 8.374 0 18.234-9.841 18.234-23.262 0-13.422-10.897-23.243-30.168-23.243-31.851 0-63.898 32.047-63.898 73.113 0 32.673 16.121 52.374 44 52.374 19.017 0 35.628-14.79 44.588-32.047 1.018 14.302 7.513 21.776 17.413 21.776 8.804 0 15.925-5.243 21.364-14.458 2.094 9.645 7.65 14.36 14.87 14.36 8.275 0 15.201-5.243 21.794-14.986-.097 7.65 1.644 14.85 8.276 14.85 3.13 0 6.867-.725 7.533-3.464 6.984-28.877 24.24-52.453 29.523-52.453 1.565 0 1.995 1.507 1.995 3.287 0 7.846-5.537 23.928-5.537 34.2 0 11.092 4.716 18.43 14.459 18.43 10.8 0 21.775-13.227 29.092-32.556 2.29 18.058 7.24 32.633 14.987 32.633 9.508 0 26.392-20.014 36.625-41.203 4.01.509 10.036.372 15.827-3.717-2.465 6.241-3.912 13.07-3.912 19.897 0 19.663 9.39 25.18 17.47 25.18 8.785 0 15.907-5.243 21.365-14.458 1.8 8.315 6.398 14.34 14.85 14.34 13.225 0 24.71-13.519 24.71-24.612 0-2.934-1.252-4.715-2.72-4.715zm-274.51 18.547c-5.342 0-7.435-5.38-7.435-13.401 0-13.93 9.528-37.193 19.604-37.193 4.402 0 6.065 5.185 6.065 11.524 0 14.145-9.059 39.07-18.235 39.07zm182.948-41.574c-3.189-3.796-4.343-8.961-4.343-13.559 0-5.673 2.074-10.467 4.558-10.467 2.485 0 3.248 2.446 3.248 5.85 0 5.693-2.035 14.008-3.463 18.176zm41.418 41.574c-5.34 0-7.434-6.182-7.434-13.401 0-13.441 9.528-37.193 19.682-37.193 4.402 0 5.967 5.146 5.967 11.524 0 14.145-8.902 39.07-18.215 39.07z" fill="#fff" fill-rule="nonzero"/><defs><radialGradient id="prefix___Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="scale(1469.491) rotate(-49.416 1.37 .302)"><stop offset="0" stop-color="#6420ff"/><stop offset="1" stop-color="#6420ff" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial2" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="rotate(54.703 42.717 594.194) scale(1657.122)"><stop offset="0" stop-color="#00c4cc"/><stop offset="1" stop-color="#00c4cc" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial3" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1023 -1030 473.711 470.491 367 1684)"><stop offset="0" stop-color="#6420ff"/><stop offset="1" stop-color="#6420ff" stop-opacity="0"/></radialGradient><radialGradient id="prefix___Radial4" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(595.999 1372 -2298.41 998.431 777 256)"><stop offset="0" stop-color="#00c4cc" stop-opacity=".73"/><stop offset="0" stop-color="#00c4cc"/><stop offset="1" stop-color="#00c4cc" stop-opacity="0"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.0" id="katman_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 800 600" style="enable-background:new 0 0 800 600;" xml:space="preserve">
<style type="text/css">
.st0{fill:#0052FF;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M399.8,69.1L399.8,69.1c127.3,0,230.5,103.2,230.5,230.5l0,0c0,127.3-103.2,230.5-230.5,230.5l0,0
c-127.3,0-230.5-103.2-230.5-230.5l0,0C169.3,172.3,272.5,69.1,399.8,69.1z"/>
<path class="st1" d="M399.9,380.6c-44.8,0-81-36.3-81-81s36.3-81,81-81c40.1,0,73.4,29.2,79.8,67.5h81.6
c-6.9-83.2-76.5-148.6-161.5-148.6c-89.5,0-162.1,72.6-162.1,162.1s72.6,162.1,162.1,162.1c85,0,154.6-65.4,161.5-148.6h-81.7
C473.2,351.4,440,380.6,399.9,380.6z"/>
</svg>

After

Width:  |  Height:  |  Size: 904 B

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="300"
height="120.32412">
<path id="e"
d="m 38.866448,26.308378 c -21.145729,0 -38.76645103,8.97108 -38.76645103,36.036419 0,21.441707 11.84866003,34.944406 39.31245703,34.944406 32.326175,0 34.3984,-21.294248 34.3984,-21.294248 l -15.663557,0 c 0,0 -3.358398,11.466134 -19.690354,11.466134 -13.301932,0 -22.869203,-8.985845 -22.869203,-21.580814 l 59.861133,0 0,-7.903529 c 0,-12.460384 -7.91007,-31.668368 -36.582425,-31.668368 z m -0.546007,10.101117 c 12.662062,0 21.294248,7.757047 21.294248,19.383225 l -43.680508,0 c 0,-12.34261 11.267202,-19.383225 22.38626,-19.383225 z"
style="fill:#e53238;fill-opacity:1;stroke:none" />
<path id="b"
d="m 75.437762,0.10007279 0,83.59702321 c 0,4.745232 -0.338677,11.408082 -0.338677,11.408082 l 14.939733,0 c 0,0 0.536238,-4.785353 0.536238,-9.1587 0,0 7.381193,11.547477 27.451204,11.547477 21.13453,0 35.49041,-14.673014 35.49041,-35.695165 0,-19.556604 -13.18634,-35.28566 -35.45629,-35.28566 -20.854235,0 -27.33444,11.261381 -27.33444,11.261381 l 0,-37.67443821 z M 114.20421,36.853125 c 14.35199,0 23.47828,10.651661 23.47828,24.945665 0,15.327725 -10.54056,25.35517 -23.3759,25.35517 -15.317854,0 -23.58065,-11.960116 -23.58065,-25.218668 0,-12.354387 7.414449,-25.082167 23.47827,-25.082167 z"
style="fill:#0064d2;fill-opacity:1;stroke:none" />
<path id="a"
d="m 190.6451,26.308378 c -31.81215,0 -33.85239,17.418776 -33.85239,20.202235 l 15.83418,0 c 0,0 0.83034,-10.169369 16.9262,-10.169369 10.45935,0 18.56422,4.787411 18.56422,13.991413 l 0,3.276038 -18.56422,0 c -24.64532,0 -37.67444,7.20973 -37.67444,21.840254 0,14.398537 12.03849,22.232696 28.30702,22.232696 22.17148,0 29.31371,-12.251017 29.31371,-12.251017 0,4.872784 0.37568,9.67455 0.37568,9.67455 l 14.07643,0 c 0,0 -0.54601,-5.951939 -0.54601,-9.759864 l 0,-32.913945 c 0,-21.581223 -17.40751,-26.122991 -32.76038,-26.122991 z m 17.47221,37.128431 0,4.368051 c 0,5.697129 -3.51553,19.860981 -24.21197,19.860981 -11.3333,0 -16.1925,-5.656156 -16.1925,-12.216892 0,-11.935273 16.36378,-12.01214 40.40447,-12.01214 z"
style="fill:#f5af02;fill-opacity:1;stroke:none" />
<path id="y"
d="m 214.87901,29.041161 17.81346,0 25.56479,51.217345 25.5063,-51.217345 16.13644,0 -46.45929,91.183029 -16.9262,0 13.40641,-25.418513 z"
style="fill:#86b817;fill-opacity:1;stroke:none" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 400 400"><path fill="#150A45" d="M0 0h400v400H0z"/><path fill="#fff" d="M234.117 91.106C225.896 77.188 211.92 69 192.19 69 155.196 69 133 98.475 133 139.413c0 40.937 22.196 70.412 59.19 70.412 6.577 0 13.976-.819 19.73-3.275 6.577-2.456 12.332-6.55 16.442-11.463v19.65c0 13.1-7.399 29.475-29.595 29.475-17.264 0-25.485-9.825-28.773-19.65L139.577 239.3c8.221 18.012 26.306 34.388 61.656 34.388 51.792 0 65.767-35.207 65.767-65.5V70.638h-32.061l-.822 20.468Zm-33.706 86.788c-18.908 0-29.595-14.738-29.595-38.481 0-23.744 10.687-38.482 29.595-38.482s29.595 14.738 29.595 38.482c0 22.925-10.687 38.481-29.595 38.481ZM267 284.332V331H133.822v-27.837h106.049v-18.831H267Z"/></svg>

After

Width:  |  Height:  |  Size: 739 B

View File

@@ -0,0 +1,23 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 333.8 127" style="enable-background:new 0 0 333.8 127;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F45D48;}
</style>
<g>
<path class="st0" d="M296.4,18.6c-20.6,0-37.4,16.8-37.4,37.6c0,20.7,16.8,37.6,37.4,37.6c20.6,0,37.4-16.9,37.4-37.6
C333.8,35.4,317,18.6,296.4,18.6L296.4,18.6z M296.4,75.9c-10.9,0-19.7-8.9-19.7-19.8c0-10.9,8.8-19.8,19.7-19.8
c10.8,0,19.7,8.9,19.7,19.8C316.1,67,307.2,75.9,296.4,75.9L296.4,75.9z M186.2,48.5l-6.5-3.3c-2.8-1.4-4.6-2.5-5.3-3.3
c-0.7-0.7-1.1-1.7-1.1-2.6c0-1.3,0.6-2.3,1.7-3.2c1.1-0.9,2.7-1.3,4.7-1.3c3.7,0,7.7,2.2,12.2,6.7l11-11.1
c-2.7-3.7-6.3-6.7-10.4-8.8c-4-2-8.5-3.1-13-3c-6.7,0-12.3,2-16.6,6c-4.3,4-6.5,8.8-6.5,14.5c0,8.7,5.7,16,17.2,21.8l6,3
c5.2,2.6,7.8,5.3,7.8,8.1c0,1.5-0.7,2.8-2.1,4c-1.4,1.1-3.3,1.7-5.7,1.7c-2.2,0-4.7-0.8-7.5-2.3c-2.8-1.5-5.3-3.5-7.3-5.9l-11,12
c6.2,8.1,14.5,12.2,24.8,12.2c7.8,0,14-2.1,18.6-6.4c4.6-4.2,7-9.5,7-15.9c0-4.8-1.3-8.9-3.8-12.5C197.8,55.4,193.1,52,186.2,48.5
L186.2,48.5z M75.2,85.5V20.1H57.4v4.4c-6-3.9-13-5.9-20.1-5.9C16.8,18.6,0,35.4,0,56.1c0,20.7,16.8,37.6,37.4,37.6
c7,0,13.9-2,19.9-5.7c0,0.6,0,1.1,0,1.4c0,10.9-8.8,19.8-19.7,19.8c-3.7,0-7.3-1.1-10.4-3l-8.8,15.4c5.8,3.5,12.4,5.4,19.2,5.4
c20.6,0,37.6-16.9,37.6-37.6C75.2,88.6,75.3,86.3,75.2,85.5L75.2,85.5z M37.4,75.9c-10.8,0-19.7-8.9-19.7-19.8
c0-10.9,8.8-19.8,19.7-19.8c10.9,0,19.7,8.9,19.7,19.8C57.1,67,48.2,75.9,37.4,75.9L37.4,75.9z M145.9,20h-17.8v35.7
c0,5.1,0,13.8-3.7,17.6c-1.8,1.8-3.7,3.4-7.8,3.4c-4.1,0-6.1-1.6-7.9-3.4c-3.7-3.7-3.7-12.5-3.7-17.6V20H87.2v35.6
c-0.1,7.4-0.1,21,8.8,30c5.2,5.3,11.5,8,20.5,8c9,0,15.3-2.7,20.5-8c8.9-9,8.9-22.7,8.8-30L145.9,20z M247,72.7
c-1.4,1.1-4.9,3.5-8.8,3c-3.2-0.4-5.8-2.8-6.2-10.7V35.3h21V20h-21V0h-17.8v3.9h0v58.7c0,9.3,2.3,31,24,31
c10.9-0.2,17.6-6.1,20.4-8.5l0.4-0.3l-10.7-13.2C248.2,71.8,247.5,72.4,247,72.7L247,72.7z M247,72.7">
</path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 90"><path fill="#FFF" d="M0 0h90v90H0z"/><path fill="#FFF" d="M33.258 25.45h26.027v33.51H33.258z"/><path d="m39.22 53.522 2.194-12.578a6.684 6.684 0 0 1-2.21-7.444 6.645 6.645 0 0 1 6.311-4.495v-13.96H28.567c-3.627 0-6.567 2.952-6.567 6.593v32.324c-.005.61.13 1.213.394 1.762C23.816 58.61 29.43 67.876 45.5 75V54.038a36.635 36.635 0 0 1-6.28-.516Z" fill="#E21D3E"/><path d="M62.417 15H45.5v13.96a6.644 6.644 0 0 1 6.302 4.458 6.684 6.684 0 0 1-2.14 7.435l2.193 12.638c-2.098.367-4.225.55-6.355.547V74.97c16.055-7.094 21.684-16.39 23.106-19.276.264-.55.399-1.153.394-1.762v-32.31a6.606 6.606 0 0 0-1.918-4.686A6.555 6.555 0 0 0 62.417 15Z" fill="#B51E23"/></svg>

After

Width:  |  Height:  |  Size: 717 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -19,6 +19,20 @@
"pleaseVerifyDetails": "Моля, проверете данните и опитайте отново",
"codeIssuerHint": "Издател",
"codeSecretKeyHint": "Код",
"secret": "Код",
"all": "Всички",
"notes": "Бележки",
"notesLengthLimit": "Бележките могат да съдържат най-много {count} знака",
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"codeAccountHint": "Профил (you@domain.com)",
"codeTagHint": "Етикет",
"accountKeyType": "Тип на кода",
@@ -34,6 +48,9 @@
"nextTotpTitle": "следващ",
"deleteCodeTitle": "Изтриване на кода?",
"deleteCodeMessage": "Сигурни ли сте, че искате да изтриете този код? Това действие е необратимо.",
"trashCode": "Преместване в кошчето на кода?",
"trashCodeMessage": "Сигурни ли сте, че искате да изместите кода в кошчето за {account}?",
"trash": "Кошче",
"viewLogsAction": "Преглед на историята на действията",
"sendLogsDescription": "Това ще изпрати файлове с история на действията, за да ни помогне да отстраним проблема Ви. Въпреки че вземаме предпазни мерки, за да гарантираме, че чувствителната информация не се регистрира, препоръчваме Ви да прегледате тези файлове с история на действията, преди да ги споделите.",
"preparingLogsTitle": "Подготвяне на файловете с историята...",
@@ -100,6 +117,7 @@
"emailVerificationToggle": "Потвърждение чрез имейл",
"emailVerificationEnableWarning": "За да избегнете загуба на достъп до акаунта си, не забравяйте да съхраните копие от Вашия имейл 2FA извън Ente Auth, преди да активирате потвърждението чрез имейл.",
"authToChangeEmailVerificationSetting": "Моля, удостоверете се, за да промените потвърждението чрез имейл",
"authenticateGeneric": "Моля, удостоверете се",
"authToViewYourRecoveryKey": "Моля, удостоверете се, за да видите Вашия ключ за възстановяване",
"authToChangeYourEmail": "Моля, удостоверете се, за да промените своя имейл",
"authToChangeYourPassword": "Моля, удостоверете се, за да промените паролата си",
@@ -137,6 +155,8 @@
"leaveFamily": "Напуснете семейството",
"leaveFamilyMessage": "Сигурни ли сте, че искате да напуснете семейния план?",
"inFamilyPlanMessage": "Вие сте на семеен план!",
"hintForMobile": "Натиснете продължително код, за да го редактирате или премахнете.",
"hintForDesktop": "Натиснете десен бутон върху код, за да го редактирате или премахнете.",
"scan": "Сканиране",
"scanACode": "Скениране на код",
"verify": "Потвърждаване",
@@ -192,6 +212,10 @@
"scanAQrCode": "Скениране на QR код",
"enterDetailsManually": "Въведете подробности ръчно",
"edit": "Редактиране",
"share": "Споделяне",
"shareCodes": "Споделяне на кодове",
"shareCodesDuration": "Изберете продължителността, за която искате да споделите кодовете.",
"restore": "Възстановяване",
"copiedToClipboard": "Копирано в буферната памет",
"copiedNextToClipboard": "Следващият код е копиран в буферната памет",
"error": "Грешка",
@@ -345,6 +369,7 @@
"sigInBackupReminder": "Моля, експортирайте Вашите кодове, за да сте сигурни, че имате резервно копие, от което можете да ги възстановите.",
"offlineModeWarning": "Избрахте да продължите без резервни копия. Моля, направете ръчни резервни копия, за да сте сигурни, че Вашите кодове са в безопасност.",
"showLargeIcons": "Показване на големи икони",
"compactMode": "Компактен изглед",
"shouldHideCode": "Скриване на кодове",
"doubleTapToViewHiddenCode": "Можете да докоснете два пъти върху запис, за да видите кода",
"focusOnSearchBar": "Фокусиране на търсенето при стартиране на приложението",
@@ -465,5 +490,7 @@
"pinLock": "Заключване с ПИН код",
"enterPin": "Въведете ПИН код",
"setNewPin": "Задаване на нов ПИН код",
"importFailureDescNew": "Неуспешно обработване на избрания файл."
"importFailureDescNew": "Неуспешно обработване на избрания файл.",
"appLockNotEnabled": "Заключването на приложението не е активирано",
"appLockNotEnabledDescription": "Моля, активирайте заключването на приложението от Сигурност > Заключване на приложението"
}

View File

@@ -19,6 +19,20 @@
"pleaseVerifyDetails": "Παρακαλούμε επιβεβαιώστε τα στοιχεία σας και προσπαθήστε ξανά",
"codeIssuerHint": "Εκδότης",
"codeSecretKeyHint": "Μυστικό Κλειδί",
"secret": "Μυστικό",
"all": "Όλα",
"notes": "Σημειώσεις",
"notesLengthLimit": "Οι σημειώσεις μπορούν να είναι το πολύ {count} χαρακτήρες",
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"codeAccountHint": "Λογαριασμός (you@domain.com)",
"codeTagHint": "Ετικέτα",
"accountKeyType": "Τύπος κλειδιού",
@@ -34,6 +48,9 @@
"nextTotpTitle": "επόμενο",
"deleteCodeTitle": "Διαγραφή κωδικού;",
"deleteCodeMessage": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον κωδικό; Αυτή η ενέργεια είναι μη αναστρέψιμη.",
"trashCode": "Διαγραφή κώδικα;",
"trashCodeMessage": "Είστε σίγουροι ότι θέλετε να διαγράψετε τον κώδικα για το {account} σας;",
"trash": "Διαγραφή",
"viewLogsAction": "Προβολή αρχείων καταγραφής",
"sendLogsDescription": "Αυτό θα στείλει σε μας όλα τα αρχεία καταγραφής για να μας βοηθήσει να αποσφαλματώσουμε το πρόβλημά σας. Ενώ λαμβάνουμε προφυλάξεις για να διασφαλίσουμε ότι οι ευαίσθητες πληροφορίες δεν καταγράφονται, σας ενθαρρύνουμε να δείτε αυτά τα αρχεία καταγραφής πριν τα μοιραστείτε.",
"preparingLogsTitle": "Προετοιμασία αρχείων καταγραφής...",
@@ -71,7 +88,7 @@
"useRecoveryKey": "Χρήση κλειδιού ανάκτησης",
"incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης",
"welcomeBack": "Καλωσορίσατε και πάλι!",
"madeWithLoveAtPrefix": "γίνεται με ❤️ στο ",
"madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ",
"supportDevs": "Εγγραφείτε στο <bold-green>ente</bold-green> για να μας υποστηρίξετε",
"supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να πάρετε 10% από το πρώτο έτος",
"changeEmail": "Αλλαγή διεύθυνσης ηλ. ταχυδρομείου",
@@ -100,6 +117,7 @@
"emailVerificationToggle": "Επαλήθευση διεύθυνσης ηλ. ταχυδρομείου",
"emailVerificationEnableWarning": "Για να αποφύγετε να κλειδωθείτε έξω από τον λογαριασμό σας, φροντίστε να αποθηκεύσετε ένα αντίγραφο του 2FA του ηλ. ταχυδρομείου σας έξω από το Ente Auth πριν ενεργοποιήσετε την επαλήθευση μέσω ηλ. ταχυδρομείου.",
"authToChangeEmailVerificationSetting": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε την επαλήθευση ηλ. ταχυδρομείου",
"authenticateGeneric": "Παρακαλώ πιστοποιήστε την ταυτότητά σας",
"authToViewYourRecoveryKey": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να δείτε το κλειδί ανάκτησης",
"authToChangeYourEmail": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε τη διεύθυνση ηλ. ταχυδρομείου σας",
"authToChangeYourPassword": "Παρακαλώ πραγματοποιήστε έλεγχο ταυτότητας για να αλλάξετε τον κωδικό πρόσβασής σας",
@@ -137,6 +155,8 @@
"leaveFamily": "Αποχώρηση από οικογένεια",
"leaveFamilyMessage": "Είστε σίγουροι ότι θέλετε να φύγετε από το οικογενειακό πρόγραμμα;",
"inFamilyPlanMessage": "Είστε σε οικογενειακό πρόγραμμα!",
"hintForMobile": "Πατήστε παρατεταμένα σε έναν κωδικό για να τον τροποποιήσετε ή να τον διαγράψετε.",
"hintForDesktop": "Κάντε δεξί κλικ σε έναν κωδικό για να τον τροποποιήσετε ή να τον διαγράψετε.",
"scan": "Σάρωση",
"scanACode": "Σάρωση κωδικού",
"verify": "Επαλήθευση",
@@ -192,6 +212,10 @@
"scanAQrCode": "Σαρώστε έναν κωδικό QR",
"enterDetailsManually": "Χειροκίνητη εισαγωγή στοιχείων",
"edit": "Επεξεργασία",
"share": "Κοινοποίηση",
"shareCodes": "Κοινοποίηση κωδικών",
"shareCodesDuration": "Επιλέξτε τη διάρκεια για την οποία θέλετε να μοιραστείτε τους κωδικούς.",
"restore": "Επαναφορά",
"copiedToClipboard": "Αντιγράφηκε στο πρόχειρο",
"copiedNextToClipboard": "Αντιγράφηκε ο επόμενος κωδικός στο πρόχειρο",
"error": "Σφάλμα",
@@ -345,6 +369,7 @@
"sigInBackupReminder": "Παρακαλώ εξάγετε τους κωδικούς σας για να βεβαιωθείτε ότι έχετε ένα αντίγραφο ασφαλείας από το οποίο μπορείτε να επαναφέρετε.",
"offlineModeWarning": "Επιλέξατε να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Παρακαλώ κάντε χειροκίνητα αντίγραφα ασφαλείας για να βεβαιωθείτε ότι οι κωδικοί σας είναι ασφαλείς.",
"showLargeIcons": "Εμφάνιση μεγάλων εικονιδίων",
"compactMode": "Συμπαγής λειτουργία",
"shouldHideCode": "Απόκρυψη κωδικών",
"doubleTapToViewHiddenCode": "Μπορείτε να πατήσετε δύο φορές σε μια καταχώρηση για να δείτε τον κωδικό",
"focusOnSearchBar": "Εστίαση στην αναζήτηση κατά την εκκίνηση εφαρμογής",
@@ -465,5 +490,7 @@
"pinLock": "Κλείδωμα καρφιτσωμάτων",
"enterPin": "Εισαγωγή PIN",
"setNewPin": "Ορίστε νέο PIN",
"importFailureDescNew": "Αδυναμία ανάλυσης του επιλεγμένου αρχείου."
"importFailureDescNew": "Αδυναμία ανάλυσης του επιλεγμένου αρχείου.",
"appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο",
"appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής"
}

View File

@@ -23,16 +23,16 @@
"all": "All",
"notes": "Notes",
"notesLengthLimit": "Notes can be at most {count} characters long",
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"codeAccountHint": "Account (you@domain.com)",
"codeTagHint": "Tag",
"accountKeyType": "Type of key",
@@ -141,16 +141,6 @@
"oops": "Oops",
"suggestFeatures": "Suggest features",
"faq": "FAQ",
"faq_q_1": "How secure is Auth?",
"faq_a_1": "All codes you backup via Auth is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
"faq_q_2": "Can I access my codes on desktop?",
"faq_a_2": "You can access your codes on the web @ auth.ente.io.",
"faq_q_3": "How can I delete codes?",
"faq_a_3": "You can delete a code by swiping left on that item.",
"faq_q_4": "How can I support this project?",
"faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
"faq_q_5": "How can I enable FaceID lock in Auth",
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
"somethingWentWrongMessage": "Something went wrong, please try again",
"leaveFamily": "Leave family",
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
@@ -492,5 +482,6 @@
"setNewPin": "Set new PIN",
"importFailureDescNew": "Could not parse the selected file.",
"appLockNotEnabled": "App lock not enabled",
"appLockNotEnabledDescription": "Please enable app lock from Security > App Lock"
"appLockNotEnabledDescription": "Please enable app lock from Security > App Lock",
"authToViewPasskey": "Please authenticate to view passkey"
}

View File

@@ -19,6 +19,20 @@
"pleaseVerifyDetails": "Verifica i dettagli e riprova",
"codeIssuerHint": "Emittente",
"codeSecretKeyHint": "Codice segreto",
"secret": "Segreto",
"all": "Tutto",
"notes": "Note",
"notesLengthLimit": "Le note possono essere al massimo {count} caratteri",
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"codeAccountHint": "Account (username@dominio.it)",
"codeTagHint": "Tag",
"accountKeyType": "Tipo di chiave",
@@ -34,6 +48,9 @@
"nextTotpTitle": "successivo",
"deleteCodeTitle": "Eliminare il codice?",
"deleteCodeMessage": "Sei sicuro di voler rimuovere questo codice? L'azione è irreversibile.",
"trashCode": "Codice del cestino?",
"trashCodeMessage": "Sei sicuro di voler cestinare il codice per {account}?",
"trash": "Cestino",
"viewLogsAction": "Visualizza i log",
"sendLogsDescription": "Invierai i tuoi log per aiutarci a risolvere il tuo problema. Prendiamo precauzioni per garantire che le informazioni sensibili non siano registrate, tuttavia ti invitiamo a leggerli prima di condividerli con noi.",
"preparingLogsTitle": "Preparando i log...",
@@ -100,6 +117,7 @@
"emailVerificationToggle": "Verifica email",
"emailVerificationEnableWarning": "Se memorizzate il 2FA per accedere alla vostra email con noi, l'attivazione della verifica dell'email potrebbe provocare dei problemi. Se siete rimasti bloccati fuori da un servizio, potreste non essere in grado di accedere anche all'altro.",
"authToChangeEmailVerificationSetting": "Autenticati per cambiare la verifica email",
"authenticateGeneric": "Si prega di autenticarsi",
"authToViewYourRecoveryKey": "Autenticati per visualizzare la tua chiave di recupero",
"authToChangeYourEmail": "Autenticati per cambiare la tua email",
"authToChangeYourPassword": "Autenticati per cambiare la tua password",
@@ -137,6 +155,8 @@
"leaveFamily": "Abbandona il piano famiglia",
"leaveFamilyMessage": "Sei sicuro di voler uscire dal piano famiglia?",
"inFamilyPlanMessage": "Sei un utente con piano famiglia!",
"hintForMobile": "Premi a lungo su un codice per modificare o rimuovere.",
"hintForDesktop": "Fare clic con il tasto destro su un codice per modificare o rimuovere.",
"scan": "Scansiona",
"scanACode": "Scansiona un codice",
"verify": "Verifica",
@@ -192,6 +212,10 @@
"scanAQrCode": "Scansiona un codice QR",
"enterDetailsManually": "Inserisci i dettagli manualmente",
"edit": "Modifica",
"share": "Condividi",
"shareCodes": "Condividi codice",
"shareCodesDuration": "Seleziona la durata per la quale vuoi condividere i codici.",
"restore": "Ripristina",
"copiedToClipboard": "Copiato negli appunti",
"copiedNextToClipboard": "Copiato il codice successivo negli appunti",
"error": "Errore",
@@ -345,6 +369,7 @@
"sigInBackupReminder": "Si prega di esportare i codici per assicurarsi di avere un backup da cui è possibile ripristinare.",
"offlineModeWarning": "Hai scelto di procedere senza backup. Si prega di eseguire backup manuali per assicurarsi che i codici siano al sicuro.",
"showLargeIcons": "Mostra icone grandi",
"compactMode": "Modalità compatta",
"shouldHideCode": "Nascondi i codici",
"doubleTapToViewHiddenCode": "Puoi toccare due volte una voce per visualizzare il codice",
"focusOnSearchBar": "Apri ricerca all'avvio dell'app",

View File

@@ -88,6 +88,7 @@
"faq": "FAQ",
"scan": "Skanna",
"twoFactorAuthTitle": "Tvåfaktorsautentisering",
"verifyPasskey": "Verifiera nyckel",
"enterRecoveryKeyHint": "Ange din återställningsnyckel",
"invalidQRCode": "Ogiltig QR-kod",
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
@@ -200,6 +201,7 @@
"noInternetConnection": "Ingen internetanslutning",
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
"signOutOtherDevices": "Logga ut andra enheter",
"passkey": "Nyckel",
"loginSessionExpiredDetails": "Din session har upphört. Logga in igen.",
"tags": "Taggar",
"createNewTag": "Skapa ny tagg",

View File

@@ -19,6 +19,20 @@
"pleaseVerifyDetails": "Lütfen bilgileri doğrulayın ve tekrar deneyin",
"codeIssuerHint": "Yayınlayan",
"codeSecretKeyHint": "Gizli Anahtar",
"secret": "Gizli",
"all": "Tümü",
"notes": "Notlar",
"notesLengthLimit": "Notlar en fazla {count} karakter uzunluğunda olabilir",
"@notesLengthLimit": {
"description": "Text to indicate the maximum number of characters allowed for notes",
"placeholders": {
"count": {
"description": "The maximum number of characters allowed for notes",
"type": "int",
"example": "100"
}
}
},
"codeAccountHint": "Hesap (ornek@domain.com)",
"codeTagHint": "Etiket",
"accountKeyType": "Anahtar türü",
@@ -34,6 +48,9 @@
"nextTotpTitle": "sonraki",
"deleteCodeTitle": "Kodu silmek istiyor musunuz?",
"deleteCodeMessage": "Bu kodu silmek istediğinize emin misiniz? Bu geri alınamaz bir işlemdir.",
"trashCode": "Kod çöpe atılsın mı?",
"trashCodeMessage": "{account} için kodu çöpe atmak istediğinize emin misiniz?",
"trash": "Çöp",
"viewLogsAction": "Günlüğü görüntüle",
"sendLogsDescription": "Günlüğünüz hatanızı çözmemize yardımcı olacaktır. Hassas bilginin kaydedilmediğine dikkat etsek de bu günlükleri paylaşmadan önce kontrol etmenizi isteriz.",
"preparingLogsTitle": "Günlük hazırlanıyor...",
@@ -100,6 +117,7 @@
"emailVerificationToggle": "E-posta doğrulama",
"emailVerificationEnableWarning": "E-postanız için 2FA'yı bizde saklıyorsanız, e-posta doğrulamasını açmak bir kilitlenmeye neden olabilir. Bir hizmetin dışında kalırsanız, diğerine giriş yapamayabilirsiniz.",
"authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın",
"authenticateGeneric": "Lütfen doğrulama yapın",
"authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourEmail": "Epostanızı değiştirmek için lütfen kimliğinizi doğrulayın",
"authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimliğinizi doğrulayın",
@@ -137,6 +155,8 @@
"leaveFamily": "Aile planından ayrıl",
"leaveFamilyMessage": "Aile planından ayrılmak istediğinize emin misiniz?",
"inFamilyPlanMessage": "Aile planı kullanıyorsunuz!",
"hintForMobile": "Bir koda uzun basarak düzenleyin veya silin.",
"hintForDesktop": "Bir koda sağ tıklayıp düzenleyin veya silin.",
"scan": "Tara",
"scanACode": "Bir kodu tarayın",
"verify": "Doğrula",
@@ -192,6 +212,10 @@
"scanAQrCode": "Bir QR kod tarayın",
"enterDetailsManually": "Bilgileri elle girin",
"edit": "Düzenle",
"share": "Paylaş",
"shareCodes": "Kodları paylaş",
"shareCodesDuration": "Kodları paylaşma süresini seçin.",
"restore": "Geri yükle",
"copiedToClipboard": "Panoya kopyalandı",
"copiedNextToClipboard": "Sonraki kod panoya kopyalandı",
"error": "Hata",
@@ -345,6 +369,7 @@
"sigInBackupReminder": "Geri yükleyebileceğiniz bir yedeğiniz olduğundan emin olmak için lütfen kodlarınızı dışa aktarın.",
"offlineModeWarning": "Yedekleme yapmadan devam etmeyi seçtiniz. Kodlarınızın güvende olduğundan emin olmak için lütfen manuel yedekleme yapın.",
"showLargeIcons": "Büyük simgeler göster",
"compactMode": "Sıkıştırılmış mod",
"shouldHideCode": "Kodları gizle",
"doubleTapToViewHiddenCode": "Kodu görüntülemek için bir girdiye çift dokunabilirsiniz",
"focusOnSearchBar": "Uygulama başladığında arama bölümüne odaklan",
@@ -465,5 +490,7 @@
"pinLock": "Pin kilidi",
"enterPin": "PIN Girin",
"setNewPin": "Yeni PIN belirleyin",
"importFailureDescNew": "Seçilen dosya ayrıştırılamadı."
"importFailureDescNew": "Seçilen dosya ayrıştırılamadı.",
"appLockNotEnabled": "Uygulama kilidi etkin değil",
"appLockNotEnabledDescription": "Uygulama kilidini Güvenlik -> Uygulama Kilidi üzerinden etkinleştirin"
}

View File

@@ -155,6 +155,8 @@
"leaveFamily": "离开家庭",
"leaveFamilyMessage": "您确定要离开家庭计划吗?",
"inFamilyPlanMessage": "你在一个家庭计划中!",
"hintForMobile": "长按代码即可编辑或删除。",
"hintForDesktop": "右键单击代码即可编辑或删除。",
"scan": "扫描",
"scanACode": "扫描代码",
"verify": "验证",

View File

@@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart';
// Add more language to the list only when at least 90% of the strings are
// translated in the corresponding language.
const List<Locale> appSupportedLocales = <Locale>[
Locale('bg'),
Locale('de'),
Locale('en'),
Locale('es', 'ES'),

View File

@@ -28,10 +28,14 @@ class Code {
bool get isPinned => display.pinned;
bool get isTrashed => display.trashed;
String get note => display.note;
final Object? err;
bool get hasError => err != null;
String get issuerAccount =>
account.isNotEmpty ? '$issuer ($account)' : issuer;
Code(
this.account,
this.issuer,
@@ -170,8 +174,10 @@ class Code {
if (uri.queryParameters.containsKey("issuer") && !path.contains(":")) {
return path;
}
return path.split(':')[1];
} catch (e) {
return path
.substring(path.indexOf(':') + 1); // return data after first colon
} catch (e, s) {
Logger('_getAccount').severe('Error while parsing account', e, s);
return "";
}
}

View File

@@ -8,6 +8,7 @@ import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/locale.dart';
import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/theme/text_style.dart';
import 'package:ente_auth/ui/account/email_entry_page.dart';
import 'package:ente_auth/ui/account/login_page.dart';
@@ -49,6 +50,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
await autoLogoutAlert(context);
});
super.initState();
PreferenceService.instance.configureDefaults().ignore();
}
@override

View File

@@ -57,6 +57,12 @@ class PreferenceService {
await _prefs.setBool(kCompactMode, value);
}
Future<void> configureDefaults() async {
if (!_prefs.containsKey(kCompactMode)) {
await _prefs.setBool(kCompactMode, true);
}
}
Future<void> setHideCodes(bool value) async {
await _prefs.setBool(kShouldHideCodesKey, value);
Bus.instance.fire(IconsChangedEvent());

View File

@@ -611,11 +611,13 @@ class _CodeWidgetState extends State<CodeWidget> {
}
FocusScope.of(context).requestFocus();
final l10n = context.l10n;
final String issuerAccount = widget.code.account.isNotEmpty
? '${widget.code.issuer} (${widget.code.account})'
: widget.code.issuer;
await showChoiceActionSheet(
context,
title: l10n.trashCode,
body: l10n
.trashCodeMessage('${widget.code.issuer} (${widget.code.account})'),
body: l10n.trashCodeMessage(issuerAccount),
firstButtonLabel: l10n.trash,
isCritical: true,
firstButtonOnTap: () async {

View File

@@ -24,34 +24,49 @@ class _ActionBarWidgetState extends State<ActionBarWidget> {
return SizedBox(
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 20, 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Column(
// left align the text
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 1,
child: Text(
widget.code.issuer,
if (widget.code.note.isNotEmpty)
SelectableText(
widget.code.note,
style: textTheme.miniMuted,
),
),
Flexible(
flex: 1,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
widget.onCancel?.call();
},
child: Align(
alignment: Alignment.centerRight,
child: Text(
context.l10n.cancel,
style: textTheme.mini,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
flex: 1,
child: Column(
children: [
SelectableText(
widget.code.issuerAccount,
style: textTheme.miniMuted,
),
],
),
),
Flexible(
flex: 1,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
widget.onCancel?.call();
},
child: Align(
alignment: Alignment.centerRight,
child: Text(
context.l10n.cancel,
style: textTheme.mini,
),
),
),
),
),
),
],
),
],
),

View File

@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:ente_auth/models/code.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/components/actions_bar_widget.dart';
@@ -37,9 +39,12 @@ class BottomActionBarWidget extends StatelessWidget {
final bottomPadding = MediaQuery.of(context).padding.bottom;
final widthOfScreen = MediaQuery.of(context).size.width;
final colorScheme = getEnteColorScheme(context);
final double leftRightPadding = widthOfScreen > restrictedMaxWidth
? (widthOfScreen - restrictedMaxWidth) / 2
: 0;
final double leftRightPadding = min(
widthOfScreen > restrictedMaxWidth
? (widthOfScreen - restrictedMaxWidth) / 2
: 0,
20,
);
return Container(
decoration: BoxDecoration(
color: backgroundColor ?? colorScheme.backgroundElevated2,

View File

@@ -1,10 +1,10 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:ente_auth/ui/settings/faq.dart';
import 'package:ente_auth/utils/navigation_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class HomeEmptyStateWidget extends StatelessWidget {
final VoidCallback? onScanTap;
@@ -73,15 +73,16 @@ class HomeEmptyStateWidget extends StatelessWidget {
const SizedBox(height: 18),
InkWell(
onTap: () {
showModalBottomSheet<void>(
backgroundColor:
Theme.of(context).colorScheme.surface,
barrierColor: Colors.black87,
context: context,
builder: (context) {
return const FAQQuestionsWidget();
},
);
try {
PlatformUtil.openWebView(
context,
context.l10n.faq,
"https://help.ente.io/auth/faq",
);
} catch (e) {
Logger("HomeEmptyStateWidget")
.severe("Failed to open FAQ", e);
}
},
child: Text(
l10n.faq,

View File

@@ -461,7 +461,7 @@ class _HomePageState extends State<HomePage> {
return ClipRect(
child: CodeWidget(
key: ValueKey(code.hashCode),
key: ValueKey('${code.hashCode}_$newIndex'),
code,
isCompactMode: isCompactMode,
),
@@ -493,6 +493,7 @@ class _HomePageState extends State<HomePage> {
itemBuilder: ((context, index) {
final codeState = _filteredCodes[index];
return CodeWidget(
key: ValueKey('${codeState.hashCode}_$index'),
codeState,
isCompactMode: isCompactMode,
);

View File

@@ -1,124 +0,0 @@
import 'dart:convert';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/loading_widget.dart';
import 'package:expansion_tile_card/expansion_tile_card.dart';
import 'package:flutter/material.dart';
class FAQQuestionsWidget extends StatelessWidget {
const FAQQuestionsWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return FutureBuilder<List<FaqItem>>(
future: Future.value(_getFAQs(context)),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
final faqs = <Widget>[];
faqs.add(
Padding(
padding: const EdgeInsets.all(24),
child: Text(
context.l10n.faq,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
);
for (final faq in snapshot.data) {
faqs.add(FaqWidget(faq: faq));
}
faqs.add(
const Padding(
padding: EdgeInsets.all(16),
),
);
return SingleChildScrollView(
child: Column(
children: faqs,
),
);
} else {
return const EnteLoadingWidget();
}
},
);
}
List<FaqItem> _getFAQs(BuildContext context) {
final l01n = context.l10n;
List<FaqItem> faqs = [];
faqs.add(FaqItem(q: l01n.faq_q_1, a: l01n.faq_a_1));
faqs.add(FaqItem(q: l01n.faq_q_2, a: l01n.faq_a_2));
faqs.add(FaqItem(q: l01n.faq_q_3, a: l01n.faq_a_3));
faqs.add(FaqItem(q: l01n.faq_q_4, a: l01n.faq_a_4));
faqs.add(FaqItem(q: l01n.faq_q_5, a: l01n.faq_a_5));
return faqs;
}
}
class FaqWidget extends StatelessWidget {
const FaqWidget({
super.key,
required this.faq,
});
final FaqItem? faq;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(2),
child: ExpansionTileCard(
elevation: 0,
title: Text(faq!.q),
expandedTextColor: Theme.of(context).colorScheme.alternativeColor,
baseColor: Theme.of(context).cardColor,
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 12,
),
child: Text(
faq!.a,
style: const TextStyle(
height: 1.5,
),
),
),
),
],
),
);
}
}
class FaqItem {
final String q;
final String a;
FaqItem({
required this.q,
required this.a,
});
factory FaqItem.fromMap(Map<String, dynamic> map) {
return FaqItem(
q: map['q'] ?? 'q',
a: map['a'] ?? 'a',
);
}
factory FaqItem.fromJson(String source) =>
FaqItem.fromMap(json.decode(source));
}

View File

@@ -126,6 +126,8 @@ class _ItemsWidgetState extends State<ItemsWidget> {
switch (locale.languageCode) {
case 'en':
return 'English';
case 'bg':
return 'Български';
case 'es':
switch (locale.countryCode) {
case 'ES':

View File

@@ -140,12 +140,13 @@ class CustomPinKeypad extends StatelessWidget {
}
}
class _Button extends StatelessWidget {
class _Button extends StatefulWidget {
final String number;
final String text;
final VoidCallback? onTap;
final bool muteButton;
final Widget? icon;
const _Button({
required this.number,
required this.text,
@@ -154,47 +155,78 @@ class _Button extends StatelessWidget {
this.icon,
});
@override
State<_Button> createState() => _ButtonState();
}
class _ButtonState extends State<_Button> {
bool isPressed = false;
void _onTapDown(TapDownDetails details) {
setState(() {
isPressed = true;
});
}
void _onTapUp(TapUpDetails details) async {
setState(() {
isPressed = false;
});
}
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: muteButton
? colorScheme.fillFaintPressed
: icon == null
? colorScheme.backgroundElevated2
: null,
),
child: Center(
child: muteButton
? const SizedBox.shrink()
: icon != null
? Container(
child: icon,
)
: Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
number,
style: textTheme.h3,
),
Text(
text,
style: textTheme.tinyBold,
),
],
onTap: widget.onTap,
onTapDown: _onTapDown,
onTapUp: _onTapUp,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
curve: Curves.easeOut,
child: Container(
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: isPressed
? colorScheme.backgroundElevated
: widget.muteButton
? colorScheme.fillFaintPressed
: widget.icon == null
? colorScheme.backgroundElevated2
: null,
),
child: Center(
child: widget.muteButton
? const SizedBox.shrink()
: widget.icon != null
? Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 10,
),
child: widget.icon,
)
: Container(
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
widget.number,
style: textTheme.h3,
),
Text(
widget.text,
style: textTheme.tinyBold,
),
],
),
),
),
),
),
),
),

View File

@@ -101,7 +101,18 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async => await onPasskeyClick(context),
onTap: () async {
final bool hasAuthenticated = await LocalAuthenticationService
.instance
.requestLocalAuthentication(
context,
context.l10n.authToViewPasskey,
);
await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
await onPasskeyClick(context);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
@@ -178,6 +189,15 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
Future<void> onPasskeyClick(BuildContext buildContext) async {
try {
final hasAuthenticated =
await LocalAuthenticationService.instance.requestLocalAuthentication(
context,
context.l10n.authenticateGeneric,
);
await PlatformUtil.refocusWindows();
if (!hasAuthenticated) {
return;
}
final isPassKeyResetEnabled =
await PasskeyService.instance.isPasskeyRecoveryEnabled();
if (!isPassKeyResetEnabled) {

View File

@@ -5,8 +5,8 @@ import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/faq.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher_string.dart';
@@ -42,15 +42,15 @@ class _SupportSectionWidgetState extends State<SupportSectionWidget> {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
// ignore: unawaited_futures
showModalBottomSheet<void>(
backgroundColor: Theme.of(context).colorScheme.surface,
barrierColor: Colors.black87,
context: context,
builder: (context) {
return const FAQQuestionsWidget();
},
);
try {
PlatformUtil.openWebView(
context,
context.l10n.faq,
"https://help.ente.io/auth/faq",
);
} catch (e) {
Logger("SupportSection").severe("Failed to open FAQ", e);
}
},
),
sectionOptionSpacing,

View File

@@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:math';
import 'package:confetti/confetti.dart';
@@ -14,6 +15,7 @@ import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/utils/email_util.dart';
import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -140,11 +142,19 @@ Future<ButtonResult?> showGenericErrorDialog({
bool isDismissible = true,
required Object? error,
}) async {
final errorBody = parseErrorForUI(
String errorBody = parseErrorForUI(
context,
context.l10n.itLooksLikeSomethingWentWrongPleaseRetryAfterSome,
error: error,
);
bool isWindowCertError = false;
if (Platform.isWindows &&
error != null &&
error.toString().contains("CERTIFICATE_VERIFY_FAILED")) {
isWindowCertError = true;
errorBody =
"Certificate verification failed. Please update your system certificates, & restart the app. If the issue persists, please contact support.";
}
return showDialogWidget(
context: context,
@@ -159,6 +169,20 @@ Future<ButtonResult?> showGenericErrorDialog({
buttonAction: ButtonAction.first,
isInAlert: true,
),
if (isWindowCertError)
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: 'Update Certificates',
buttonAction: ButtonAction.third,
isInAlert: true,
onTap: () async {
PlatformUtil.openWebView(
context,
context.l10n.faq,
"https://help.ente.io/auth/troubleshooting/windows-login",
);
},
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.contactSupport,

View File

@@ -1,6 +1,6 @@
name: ente_auth
description: ente two-factor authenticator
version: 3.1.8+328
version: 4.0.1+401
publish_to: none
environment:

View File

@@ -15,7 +15,7 @@ import (
"strings"
)
var AppVersion = "0.2.0"
var AppVersion = "0.2.1"
func main() {
cliDBPath, err := GetCLIConfigPath()

View File

@@ -9,7 +9,7 @@ import (
)
func MapRemoteAuthEntityToString(ctx context.Context, authEntity models.AuthEntity, authKey []byte) (*string, error) {
_, decrypted, err := eCrypto.DecryptChaChaBase64(*authEntity.EncryptedData, authKey, *authEntity.Header)
_, decrypted, err := eCrypto.DecryptChaChaBase64Auth(*authEntity.EncryptedData, authKey, *authEntity.Header)
if err != nil {
return nil, fmt.Errorf("failed to decrypt auth enityt %s: %v", authEntity.ID, err)
}

View File

@@ -3,6 +3,7 @@
## v1.7.5 (Unreleased)
- Directly upload to selected album on drag and drop.
- Include shared files in export.
- .
## v1.7.4

View File

@@ -254,6 +254,10 @@ export const sidebar = [
text: "Using external S3",
link: "/self-hosting/guides/external-s3",
},
{
text: "DB migration",
link: "/self-hosting/guides/db-migration",
},
],
},
{

View File

@@ -11,13 +11,9 @@ All codes you backup via Ente is stored end-to-end encrypted. This means only
you can access your codes. Our apps are open source and our cryptography has
been externally audited.
### Can I access my codes on desktop?
You can access your codes on the web at [auth.ente.io](https://auth.ente.io).
### How can I delete or edit codes?
You can delete or edit a code by swiping left on that item.
You can delete or edit a code long press (or right click on desktop) on that item.
### How can I support this project?
@@ -39,6 +35,10 @@ Usually this discrepancy occurs because the time in your browser might be
incorrect. In particular, multiple users who have reported that Firefox provides
incorrect time when various privacy settings are enabled.
### Can I access my codes on web?
You can access your codes on the web at [auth.ente.io](https://auth.ente.io).
### Does Ente Authenticator require an account?
Answer: No, Ente Authenticator does not require an account. You can choose to
@@ -49,6 +49,10 @@ use the app without backups if you prefer.
Yes, you can download the Ente app on multiple devices and sync the codes,
end-to-end encrypted.
### What information about my codes is stored on Ente server?
Due to E2EE, the server doesn't know anything about the code. Everything is encrypted, including the tags, type, account, issuer, notes, and pinned or trash status, etc."
### What does it mean when I receive a message saying my current device is not powerful enough to verify my password?
This means that the parameters that were used to derive your master-key on your

View File

@@ -60,6 +60,21 @@ As mentioned above, the auth data is encrypted using a key that's derived by
using user provided password & kdf params. For encryption, we are using
`XChaCha20-Poly1305` algorithm.
## Automated backups
You can use [Ente's CLI](https://github.com/ente-io/ente/tree/main/cli#readme)
to automatically backup your Auth codes.
To export your data, add an account using `ente account add` command. In the
first step, specify `auth` as the app name. At a later point, CLI will also ask
you specify the path where it should write the exported codes.
You can change the export directory using following command
```
ente account update --app auth --email <email> --dir <path>
```
## How to use the exported data
- **Ente Authenticator app**: You can directly import the codes in the Ente
@@ -67,9 +82,9 @@ using user provided password & kdf params. For encryption, we are using
> Settings -> Data -> Import Codes -> Ente Encrypted export.
- **Decrypt using Ente CLI** : Download the latest version of
[Ente CLI](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0), and run
the following command
- **Decrypt using Ente CLI** : Download the latest version of [Ente
CLI](https://github.com/ente-io/ente/releases?q=tag%3Acli-v0), and run the
following command
```
./ente auth decrypt <export_file> <output_file>

View File

@@ -176,3 +176,7 @@ you can gain more value out of a single subscription.
## Is there a forever-free plan?
Yes, we offer 5 GB of storage for free.
## What are the limitations of the free plan?
You cannot share albums, or setup a family while you are on a free plan.

View File

@@ -56,9 +56,6 @@ Code_.
</div>
Please note that referral codes should be applied within one month of account
creation to claim free storage.
---
More questions? Drop a mail to [referrals@ente.io](mailto:referrals@ente.io),

View File

@@ -93,3 +93,8 @@ implemented, are in various blog posts announcing these features.
We are now working on the other requested features around sharing, including
comments and reactions.
## Limitations
Sharing is only available to paid customers. This limitation safeguards against
potential platform abuse.

View File

@@ -0,0 +1,158 @@
---
title: DB Migration
description:
Migrating your self hosted Postgres 12 database to newer Postgres versions
---
# Migrating Postgres 12 to 15
The old sample docker compose file used Postgres 12, which is now nearing end of
life, so we've updated it to Postgres 15. Postgres major versions changes
require a migration step. This document mentions some approaches you can use.
> [!TIP]
>
> Ente itself does not use any specific Postgres 12 or Postgres 15 features, and
> will talk to either happily. It should also work with newer Postgres versions,
> but let us know if you run into any problems and we'll update this page.
### Taking a backup
`docker compose exec` allows us to run a command against a running container. We
can use it to run the `pg_dumpall` command on the postgres container to create a
plaintext backup.
Assuming your cluster is already running, and you are in the `ente/server`
directory, you can run the following (this command uses the default credentials,
you'll need to change these to match your setup):
```sh
docker compose exec postgres env PGPASSWORD=pgpass PGUSER=pguser PG_DB=ente_db pg_dumpall >pg12.backup.sql
```
This will produce a `pg12.backup.sql` in your current directory. You can open it
in a text editor (it can be huge!) to verify that it looks correct.
We won't be needing this file, this backup is recommended just in case something
goes amiss with the actual migration.
> If you need to restore from this plaintext backup, you could subsequently run
> something like:
>
> ```sh
> docker compose up postgres
> cat pg12.backup.sql | docker compose exec -T postgres env PGPASSWORD=pgpass psql -U pguser -d ente_db
> ```
## The migration
At the high level, the steps are
1. Stop your cluster.
2. Start just the postgres container after changing the image to
`pgautoupgrade/pgautoupgrade:15-bookworm`.
3. Once the in-place migration completes, stop the container, and change the
image to `postgres:15`.
#### 1. Stop the cluster
Stop your running Ente cluster.
```sh
docker compose down
```
#### 2. Run `pgautoupgrade`
Modify your `compose.yaml`, changing the image for the "postgres" container from
"postgres:12" to "pgautoupgrade/pgautoupgrade:15-bookworm"
```diff
diff a/server/compose.yaml b/server/compose.yaml
postgres:
- image: postgres:12
+ image: pgautoupgrade/pgautoupgrade:15-bookworm
ports:
```
[pgautoupgrade](https://github.com/pgautoupgrade/docker-pgautoupgrade) is a
community docker image that performs an in-place migration.
After making the change, run only the `postgres` container in the cluster
```sh
docker compose up postgres
```
The container will start and peform an in-place migration. Once it is done, it
will start postgres normally. You should see something like this is the logs
```
postgres-1 | Automatic upgrade process finished with no errors reported
...
postgres-1 | ... starting PostgreSQL 15...
```
At this point, you can stop the container (`CTRL-C`).
#### 3. Finish by changing image
Modify `compose.yaml` again, changing the image to "postgres:15".
```diff
diff a/server/compose.yaml b/server/compose.yaml
postgres:
- image: pgautoupgrade/pgautoupgrade:15-bookworm
+ image: postgres:15
ports:
```
And cleanup the temporary containers by
```sh
docker compose down --remove-orphans
```
Migration is now complete. You can start your Ente cluster normally.
```sh
docker compose up
```
## Migration elsewhere
The above instructions are for Postgres running inside docker, as the sample
docker compose file does. There are myriad other ways to run Postgres, and the
migration sequence then will depend on your exact setup.
Two common approaches are
1. Backup and restore, the `pg_dumpall` + `psql` import sequence described in
[Taking a backup](#taking-a-backup) above.
2. In place migrations using `pg_upgrade`, which is what the
[pgautoupgrade](#the-migration) migration above does under the hood.
The first method, backup and restore, is low tech and will work similarly in
most setups. The second method is more efficient, but requires a bit more
careful preparation.
As another example, here is how one can migrate 12 to 15 when running Postgres
on macOS, installed using Homebrew.
1. Stop your postgres. Make sure there are no more commands shown by
`ps aux | grep '[p]ostgres'`.
2. Install postgres15.
3. Migrate data using `pg_upgrade`:
```sh
/opt/homebrew/Cellar/postgresql@15/15.8/bin/pg_upgrade -b /opt/homebrew/Cellar/postgresql@12/12.18_1/bin -B /opt/homebrew/Cellar/postgresql@15/15.8/bin/ -d /opt/homebrew/var/postgresql@12 -D /opt/homebrew/var/postgresql@15
```
4. Start postgres 15 and verify version using `SELECT VERSION()`.

View File

@@ -3,21 +3,16 @@ FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl gnupg
RUN apt-get install -y tini
# Install pg_dump (via Postgres client)
# Install Postgres client (needed for restores, and for local testing)
# https://www.postgresql.org/download/linux/ubuntu/
#
# We don't need it for production backups, but this is useful for local testing.
RUN \
apt-get install -y lsb-release && \
sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \
curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \
apt-get update && \
apt-get -y install postgresql-client-12
# We don't have specific dependencies on Postgres, so just use the latest.
RUN apt-get update && apt-get -y install postgresql-client
# Install SCW CLI
# Latest release: https://github.com/scaleway/scaleway-cli/releases/latest
RUN \
export VERSION="2.32.1" && \
export VERSION="2.34.0" && \
curl -o /usr/local/bin/scw -L "https://github.com/scaleway/scaleway-cli/releases/download/v${VERSION}/scaleway-cli_${VERSION}_linux_amd64" && \
chmod +x /usr/local/bin/scw

View File

@@ -21,5 +21,7 @@ class MessageLookup extends MessageLookupByLibrary {
String get localeName => 'cs';
final messages = _notInlinedMessages(_notInlinedMessages);
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{};
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage("")
};
}

View File

@@ -661,6 +661,26 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Zwei-Faktor-Authentifizierung (2FA) wird deaktiviert..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("Entdecken"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Babys"),
"discover_celebrations": MessageLookupByLibrary.simpleMessage("Feiern"),
"discover_food": MessageLookupByLibrary.simpleMessage("Essen"),
"discover_greenery": MessageLookupByLibrary.simpleMessage("Grün"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Berge"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Identität"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Memes"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Notizen"),
"discover_pets": MessageLookupByLibrary.simpleMessage("Haustiere"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Belege"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Bildschirmfotos"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
"discover_sunset":
MessageLookupByLibrary.simpleMessage("Sonnenuntergang"),
"discover_visiting_cards":
MessageLookupByLibrary.simpleMessage("Visitenkarten"),
"discover_wallpapers":
MessageLookupByLibrary.simpleMessage("Hintergründe"),
"dismiss": MessageLookupByLibrary.simpleMessage("Verwerfen"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut":

View File

@@ -675,6 +675,27 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Désactiver la double-authentification..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("Découverte"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Bébés"),
"discover_celebrations": MessageLookupByLibrary.simpleMessage("Fêtes"),
"discover_food": MessageLookupByLibrary.simpleMessage("Alimentation"),
"discover_greenery": MessageLookupByLibrary.simpleMessage("Plantes"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Montagnes"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Identité"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Mèmes"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Notes"),
"discover_pets":
MessageLookupByLibrary.simpleMessage("Animaux de compagnie"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Recettes"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Captures d\'écran "),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
"discover_sunset":
MessageLookupByLibrary.simpleMessage("Coucher du soleil"),
"discover_visiting_cards":
MessageLookupByLibrary.simpleMessage("Carte de Visite"),
"discover_wallpapers":
MessageLookupByLibrary.simpleMessage("Fonds d\'écran"),
"dismiss": MessageLookupByLibrary.simpleMessage("Rejeter"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut":

View File

@@ -565,6 +565,19 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Menonaktifkan autentikasi dua langkah..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Bayi"),
"discover_food": MessageLookupByLibrary.simpleMessage("Makanan"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Bukit"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Identitas"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Meme"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Catatan"),
"discover_pets": MessageLookupByLibrary.simpleMessage("Peliharaan"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Tangkapan layar"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Swafoto"),
"discover_sunset": MessageLookupByLibrary.simpleMessage("Senja"),
"discover_wallpapers":
MessageLookupByLibrary.simpleMessage("Gambar latar"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut":
MessageLookupByLibrary.simpleMessage("Jangan keluarkan akun"),
@@ -792,6 +805,8 @@ class MessageLookup extends MessageLookupByLibrary {
"kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage(
"Harap bantu kami dengan informasi ini"),
"language": MessageLookupByLibrary.simpleMessage("Bahasa"),
"lastUpdated":
MessageLookupByLibrary.simpleMessage("Terakhir diperbaharui"),
"leave": MessageLookupByLibrary.simpleMessage("Tinggalkan"),
"leaveAlbum": MessageLookupByLibrary.simpleMessage("Tinggalkan album"),
"leaveFamily":
@@ -897,6 +912,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Pindah ke sampah"),
"movingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
"Memindahkan file ke album..."),
"name": MessageLookupByLibrary.simpleMessage("Nama"),
"networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage(
"Tidak dapat terhubung dengan Ente, silakan coba lagi setelah beberapa saat. Jika masalah berlanjut, harap hubungi dukungan."),
"networkHostLookUpErr": MessageLookupByLibrary.simpleMessage(
@@ -904,6 +920,7 @@ class MessageLookup extends MessageLookupByLibrary {
"never": MessageLookupByLibrary.simpleMessage("Tidak pernah"),
"newAlbum": MessageLookupByLibrary.simpleMessage("Album baru"),
"newToEnte": MessageLookupByLibrary.simpleMessage("Baru di Ente"),
"newest": MessageLookupByLibrary.simpleMessage("Terbaru"),
"no": MessageLookupByLibrary.simpleMessage("Tidak"),
"noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage(
"Belum ada album yang kamu bagikan"),
@@ -1040,11 +1057,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Kebijakan Privasi"),
"privateBackups":
MessageLookupByLibrary.simpleMessage("Cadangan pribadi"),
"privateSharing":
MessageLookupByLibrary.simpleMessage("Berbagi secara privat"),
"publicLinkCreated":
MessageLookupByLibrary.simpleMessage("Link publik dibuat"),
"publicLinkEnabled":
MessageLookupByLibrary.simpleMessage("Link publik aktif"),
"radius": MessageLookupByLibrary.simpleMessage("Radius"),
"raiseTicket":
MessageLookupByLibrary.simpleMessage("Buat tiket dukungan"),
"rateTheApp": MessageLookupByLibrary.simpleMessage("Nilai app ini"),
"rateUs": MessageLookupByLibrary.simpleMessage("Beri kami nilai"),
"rateUsOnStore": m47,
@@ -1197,6 +1218,8 @@ class MessageLookup extends MessageLookupByLibrary {
"sendLink": MessageLookupByLibrary.simpleMessage("Kirim link"),
"serverEndpoint":
MessageLookupByLibrary.simpleMessage("Endpoint server"),
"sessionExpired":
MessageLookupByLibrary.simpleMessage("Sesi telah berakhir"),
"setAPassword": MessageLookupByLibrary.simpleMessage("Atur sandi"),
"setAs": MessageLookupByLibrary.simpleMessage("Pasang sebagai"),
"setCover": MessageLookupByLibrary.simpleMessage("Ubah sampul"),
@@ -1295,6 +1318,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Keluarga"),
"storageBreakupYou": MessageLookupByLibrary.simpleMessage("Kamu"),
"storageInGB": m60,
"storageLimitExceeded": MessageLookupByLibrary.simpleMessage(
"Batas penyimpanan terlampaui"),
"storageUsageInfo": m61,
"strongStrength": MessageLookupByLibrary.simpleMessage("Kuat"),
"subAlreadyLinkedErrMessage": m62,
@@ -1405,6 +1430,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Pembaruan tersedia"),
"updatingFolderSelection": MessageLookupByLibrary.simpleMessage(
"Memperbaharui pilihan folder..."),
"upgrade": MessageLookupByLibrary.simpleMessage("Tingkatkan"),
"uploadingFilesToAlbum":
MessageLookupByLibrary.simpleMessage("Mengunggah file ke album..."),
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(

View File

@@ -126,7 +126,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m38(albumName) => "Spostato con successo su ${albumName}";
static String m39(name) => "Non sei ${name}?";
static String m39(name) => "Non è ${name}?";
static String m40(familyAdminEmail) =>
"Per favore contatta ${familyAdminEmail} per cambiare il tuo codice.";
@@ -659,6 +659,27 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Disattivazione autenticazione a due fattori..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("Scopri"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Neonati"),
"discover_celebrations":
MessageLookupByLibrary.simpleMessage("Festeggiamenti"),
"discover_food": MessageLookupByLibrary.simpleMessage("Cibo"),
"discover_greenery":
MessageLookupByLibrary.simpleMessage("Vegetazione"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Colline"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Identità"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Meme"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Note"),
"discover_pets":
MessageLookupByLibrary.simpleMessage("Animali domestici"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Ricette"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Schermate"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfie"),
"discover_sunset": MessageLookupByLibrary.simpleMessage("Tramonto"),
"discover_visiting_cards":
MessageLookupByLibrary.simpleMessage("Biglietti da Visita"),
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("Sfondi"),
"dismiss": MessageLookupByLibrary.simpleMessage("Ignora"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut": MessageLookupByLibrary.simpleMessage("Non uscire"),
@@ -1549,6 +1570,8 @@ class MessageLookup extends MessageLookupByLibrary {
"subAlreadyLinkedErrMessage": m62,
"subWillBeCancelledOn": m63,
"subscribe": MessageLookupByLibrary.simpleMessage("Iscriviti"),
"subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage(
"È necessario un abbonamento a pagamento attivo per abilitare la condivisione."),
"subscription": MessageLookupByLibrary.simpleMessage("Abbonamento"),
"success": MessageLookupByLibrary.simpleMessage("Operazione riuscita"),
"successfullyArchived":

View File

@@ -657,6 +657,27 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Uwierzytelnianie dwustopniowe jest wyłączane..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("Odkryj"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Niemowlęta"),
"discover_celebrations":
MessageLookupByLibrary.simpleMessage("Uroczystości"),
"discover_food": MessageLookupByLibrary.simpleMessage("Jedzenie"),
"discover_greenery": MessageLookupByLibrary.simpleMessage("Zieleń"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Wzgórza"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Tożsamość"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Memy"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Notatki"),
"discover_pets":
MessageLookupByLibrary.simpleMessage("Zwierzęta domowe"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Paragony"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Zrzuty ekranu"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfie"),
"discover_sunset":
MessageLookupByLibrary.simpleMessage("Zachód słońca"),
"discover_visiting_cards":
MessageLookupByLibrary.simpleMessage("Wizytówki"),
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("Tapety"),
"dismiss": MessageLookupByLibrary.simpleMessage("Odrzuć"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut":

View File

@@ -655,6 +655,27 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage(
"Desativando a autenticação de dois fatores..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("Explorar"),
"discover_babies": MessageLookupByLibrary.simpleMessage("Bebés"),
"discover_celebrations":
MessageLookupByLibrary.simpleMessage("Celebrações"),
"discover_food": MessageLookupByLibrary.simpleMessage("Comida"),
"discover_greenery": MessageLookupByLibrary.simpleMessage("Vegetação"),
"discover_hills": MessageLookupByLibrary.simpleMessage("Montanhas"),
"discover_identity": MessageLookupByLibrary.simpleMessage("Identidade"),
"discover_memes": MessageLookupByLibrary.simpleMessage("Memes"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Notas"),
"discover_pets":
MessageLookupByLibrary.simpleMessage("Animais de Estimação"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Recibos"),
"discover_screenshots":
MessageLookupByLibrary.simpleMessage("Capturas de Tela"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("Selfies"),
"discover_sunset": MessageLookupByLibrary.simpleMessage("Pôr do Sol"),
"discover_visiting_cards":
MessageLookupByLibrary.simpleMessage("Cartões de Visita"),
"discover_wallpapers":
MessageLookupByLibrary.simpleMessage("Papéis de Parede"),
"dismiss": MessageLookupByLibrary.simpleMessage("Descartar"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut":

View File

@@ -232,6 +232,8 @@ class MessageLookup extends MessageLookupByLibrary {
"disableDownloadWarningTitle":
MessageLookupByLibrary.simpleMessage("Vänligen notera:"),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover_notes": MessageLookupByLibrary.simpleMessage("Anteckningar"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("Kvitton"),
"doThisLater": MessageLookupByLibrary.simpleMessage("Gör detta senare"),
"done": MessageLookupByLibrary.simpleMessage("Klar"),
"dropSupportEmail": m22,
@@ -376,6 +378,7 @@ class MessageLookup extends MessageLookupByLibrary {
"oops": MessageLookupByLibrary.simpleMessage("Hoppsan"),
"orPickAnExistingOne":
MessageLookupByLibrary.simpleMessage("Eller välj en befintlig"),
"passkey": MessageLookupByLibrary.simpleMessage("Nyckel"),
"password": MessageLookupByLibrary.simpleMessage("Lösenord"),
"passwordChangedSuccessfully":
MessageLookupByLibrary.simpleMessage("Lösenordet har ändrats"),
@@ -556,6 +559,8 @@ class MessageLookup extends MessageLookupByLibrary {
"verifyEmail":
MessageLookupByLibrary.simpleMessage("Bekräfta e-postadress"),
"verifyEmailID": m70,
"verifyPasskey":
MessageLookupByLibrary.simpleMessage("Verifiera nyckel"),
"verifyPassword":
MessageLookupByLibrary.simpleMessage("Bekräfta lösenord"),
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(

View File

@@ -548,6 +548,22 @@ class MessageLookup extends MessageLookupByLibrary {
"disablingTwofactorAuthentication":
MessageLookupByLibrary.simpleMessage("正在禁用双重认证..."),
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"discover": MessageLookupByLibrary.simpleMessage("发现"),
"discover_babies": MessageLookupByLibrary.simpleMessage("婴儿"),
"discover_celebrations": MessageLookupByLibrary.simpleMessage("节日"),
"discover_food": MessageLookupByLibrary.simpleMessage("食物"),
"discover_greenery": MessageLookupByLibrary.simpleMessage("绿植"),
"discover_hills": MessageLookupByLibrary.simpleMessage(""),
"discover_identity": MessageLookupByLibrary.simpleMessage("身份"),
"discover_memes": MessageLookupByLibrary.simpleMessage("表情包"),
"discover_notes": MessageLookupByLibrary.simpleMessage("备注"),
"discover_pets": MessageLookupByLibrary.simpleMessage("宠物"),
"discover_receipts": MessageLookupByLibrary.simpleMessage("收据"),
"discover_screenshots": MessageLookupByLibrary.simpleMessage("屏幕截图"),
"discover_selfies": MessageLookupByLibrary.simpleMessage("自拍"),
"discover_sunset": MessageLookupByLibrary.simpleMessage("日落"),
"discover_visiting_cards": MessageLookupByLibrary.simpleMessage("访问卡"),
"discover_wallpapers": MessageLookupByLibrary.simpleMessage("壁纸"),
"dismiss": MessageLookupByLibrary.simpleMessage("忽略"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("公里"),
"doNotSignOut": MessageLookupByLibrary.simpleMessage("不要登出"),

View File

@@ -1,3 +1,4 @@
{
"@@locale ": "en"
"@@locale ": "en",
"incorrectRecoveryKeyBody": ""
}

View File

@@ -420,6 +420,25 @@
"mlConsentPrivacy": "Bitte klicke hier für weitere Details zu dieser Funktion in unserer Datenschutzerklärung",
"mlConsentConfirmation": "Ich verstehe und möchte das maschinelle Lernen aktivieren",
"magicSearch": "Magische Suche",
"discover": "Entdecken",
"@discover": {
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
},
"discover_identity": "Identität",
"discover_screenshots": "Bildschirmfotos",
"discover_receipts": "Belege",
"discover_notes": "Notizen",
"discover_memes": "Memes",
"discover_visiting_cards": "Visitenkarten",
"discover_babies": "Babys",
"discover_pets": "Haustiere",
"discover_selfies": "Selfies",
"discover_wallpapers": "Hintergründe",
"discover_food": "Essen",
"discover_celebrations": "Feiern",
"discover_sunset": "Sonnenuntergang",
"discover_hills": "Berge",
"discover_greenery": "Grün",
"mlIndexingDescription": "Bitte beachte, dass das maschinelle Lernen zu einem höheren Daten- und Akkuverbrauch führen wird, bis alle Elemente indiziert sind. Du kannst die Desktop-App für eine schnellere Indizierung verwenden, alle Ergebnisse werden automatisch synchronisiert.",
"loadingModel": "Lade Modelle herunter...",
"waitingForWifi": "Warte auf WLAN...",

View File

@@ -420,6 +420,25 @@
"mlConsentPrivacy": "Clicca qui per maggiori dettagli su questa funzione nella nostra informativa sulla privacy",
"mlConsentConfirmation": "Comprendo e desidero abilitare l'apprendimento automatico",
"magicSearch": "Ricerca magica",
"discover": "Scopri",
"@discover": {
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
},
"discover_identity": "Identità",
"discover_screenshots": "Schermate",
"discover_receipts": "Ricette",
"discover_notes": "Note",
"discover_memes": "Meme",
"discover_visiting_cards": "Biglietti da Visita",
"discover_babies": "Neonati",
"discover_pets": "Animali domestici",
"discover_selfies": "Selfie",
"discover_wallpapers": "Sfondi",
"discover_food": "Cibo",
"discover_celebrations": "Festeggiamenti",
"discover_sunset": "Tramonto",
"discover_hills": "Colline",
"discover_greenery": "Vegetazione",
"mlIndexingDescription": "Si prega di notare che l'attivazione dell'apprendimento automatico si tradurrà in un maggior utilizzo della connessione e della batteria fino a quando tutti gli elementi non saranno indicizzati. Valuta di utilizzare l'applicazione desktop per un'indicizzazione più veloce, tutti i risultati verranno sincronizzati automaticamente.",
"loadingModel": "Scaricamento modelli...",
"waitingForWifi": "In attesa del WiFi...",
@@ -1268,7 +1287,7 @@
"whatsNew": "Novità",
"reviewSuggestions": "Esamina i suggerimenti",
"useAsCover": "Usa come copertina",
"notPersonLabel": "Non sei {name}?",
"notPersonLabel": "Non è {name}?",
"@notPersonLabel": {
"description": "Label to indicate that the person in the photo is not the person whose name is mentioned",
"placeholders": {

View File

@@ -420,6 +420,25 @@
"mlConsentPrivacy": "Por favor, clique aqui para mais detalhes sobre este recurso em nossa política de privacidade",
"mlConsentConfirmation": "Eu entendo, e desejo habilitar o aprendizado de máquina",
"magicSearch": "Busca mágica",
"discover": "Explorar",
"@discover": {
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
},
"discover_identity": "Identidade",
"discover_screenshots": "Capturas de Tela",
"discover_receipts": "Recibos",
"discover_notes": "Notas",
"discover_memes": "Memes",
"discover_visiting_cards": "Cartões de Visita",
"discover_babies": "Bebés",
"discover_pets": "Animais de Estimação",
"discover_selfies": "Selfies",
"discover_wallpapers": "Papéis de Parede",
"discover_food": "Comida",
"discover_celebrations": "Celebrações",
"discover_sunset": "Pôr do Sol",
"discover_hills": "Montanhas",
"discover_greenery": "Vegetação",
"mlIndexingDescription": "Observe que o aprendizado de máquina resultará em uma largura de banda maior e no uso da bateria até que todos os itens sejam indexados. Considere usar o aplicativo para computador para uma indexação mais rápida, todos os resultados serão sincronizados automaticamente.",
"loadingModel": "Fazendo download de modelos...",
"waitingForWifi": "Esperando por Wi-Fi...",

View File

@@ -434,7 +434,9 @@
"contacts": "Kontakter",
"noInternetConnection": "Ingen internetanslutning",
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
"passkey": "Nyckel",
"loginSessionExpiredDetails": "Din session har upphört. Logga in igen.",
"verifyPasskey": "Verifiera nyckel",
"search": "Sök",
"whatsNew": "Nyheter",
"useAsCover": "Använd som omslag",

View File

@@ -420,6 +420,25 @@
"mlConsentPrivacy": "请点击此处查看我们隐私政策中有关此功能的更多详细信息",
"mlConsentConfirmation": "我了解了,并希望启用机器学习",
"magicSearch": "魔法搜索",
"discover": "发现",
"@discover": {
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
},
"discover_identity": "身份",
"discover_screenshots": "屏幕截图",
"discover_receipts": "收据",
"discover_notes": "备注",
"discover_memes": "表情包",
"discover_visiting_cards": "访问卡",
"discover_babies": "婴儿",
"discover_pets": "宠物",
"discover_selfies": "自拍",
"discover_wallpapers": "壁纸",
"discover_food": "食物",
"discover_celebrations": "节日",
"discover_sunset": "日落",
"discover_hills": "山",
"discover_greenery": "绿植",
"mlIndexingDescription": "请注意,机器学习会导致带宽和电池使用量增加,直到所有项目都被索引。请考虑使用桌面应用程序来加快索引速度,所有结果都将自动同步。",
"loadingModel": "正在下载模型...",
"waitingForWifi": "正在等待 WiFi...",

View File

@@ -1,5 +1,6 @@
import "dart:convert";
import "package:flutter/cupertino.dart";
import 'package:photos/models/metadata/common_keys.dart';
const editTimeKey = 'editedTime';
@@ -88,8 +89,8 @@ class PubMagicMetadata {
editedName: map[editNameKey],
caption: map[captionKey],
uploaderName: map[uploaderNameKey],
w: map[widthKey],
h: map[heightKey],
w: safeParseInt(map[widthKey], widthKey),
h: safeParseInt(map[heightKey], heightKey),
lat: map[latKey],
long: map[longKey],
mvi: map[motionVideoIndexKey],
@@ -97,4 +98,12 @@ class PubMagicMetadata {
mediaType: map[mediaTypeKey],
);
}
static int? safeParseInt(dynamic value, String key) {
if (value == null) return null;
if (value is int) return value;
debugPrint("PubMagicMetadata key: $key Unexpected value: $value");
if (value is String) return int.tryParse(value);
return null;
}
}

View File

@@ -131,6 +131,10 @@ class MLService {
);
await clusterAllImages();
}
if (_mlControllerStatus == true) {
// refresh discover section
MagicCacheService.instance.updateCache().ignore();
}
await indexAllImages();
if ((await MLDataDB.instance.getUnclusteredFaceCount()) > 0) {
await clusterAllImages();
@@ -208,6 +212,9 @@ class MLService {
);
fileAnalyzedCount += sumFutures;
}
if (fileAnalyzedCount > 0) {
MagicCacheService.instance.queueUpdate('fileIndexed');
}
_logger.info(
"`indexAllImages()` finished. Analyzed $fileAnalyzedCount images, in ${stopwatch.elapsed.inSeconds} seconds (avg of ${stopwatch.elapsed.inSeconds / fileAnalyzedCount} seconds per image)",
);

View File

@@ -174,7 +174,7 @@ class MagicCacheService {
_updateCacheIfTheTimeHasCome();
});
Bus.instance.on<FileUploadedEvent>().listen((event) {
_pendingUpdateReason.add("File uploaded");
queueUpdate("File uploaded");
});
}
@@ -191,6 +191,10 @@ class MagicCacheService {
bool get enableDiscover => localSettings.isMLIndexingEnabled;
void queueUpdate(String reason) {
_pendingUpdateReason.add(reason);
}
Future<void> _updateCacheIfTheTimeHasCome() async {
if (!enableDiscover) {
return;
@@ -198,12 +202,12 @@ class MagicCacheService {
final updatedJSONFile = await RemoteAssetsService.instance
.getAssetIfUpdated(_kMagicPromptsDataUrl);
if (updatedJSONFile != null) {
_pendingUpdateReason.add("Prompts data updated");
queueUpdate("Prompts data updated");
} else if (lastMagicCacheUpdateTime <
DateTime.now()
.subtract(const Duration(days: 1))
.millisecondsSinceEpoch) {
_pendingUpdateReason.add("Cache is old");
queueUpdate("Cache is old");
}
}
@@ -226,7 +230,6 @@ class MagicCacheService {
return;
}
_logger.info("updating magic cache ${_pendingUpdateReason.toList()}");
_pendingUpdateReason.clear();
_isUpdateInProgress = true;
final EnteWatch? w = kDebugMode ? EnteWatch("magicCacheWatch") : null;
w?.start();
@@ -245,6 +248,7 @@ class MagicCacheService {
w?.log("cacheWritten");
await _resetLastMagicCacheUpdateTime();
w?.logAndReset('done');
_pendingUpdateReason.clear();
Bus.instance.fire(MagicCacheUpdatedEvent());
} catch (e, s) {
_logger.info("Error updating magic cache", e, s);
@@ -376,6 +380,7 @@ class MagicCacheService {
List<Prompt> magicPromptsData,
) async {
final results = <MagicCache>[];
final List<int> matchCount = [];
for (Prompt prompt in magicPromptsData) {
final fileUploadedIDs =
await SemanticSearchService.instance.getMatchingFileIDs(
@@ -387,7 +392,9 @@ class MagicCacheService {
MagicCache(prompt.title, fileUploadedIDs),
);
}
matchCount.add(fileUploadedIDs.length);
}
_logger.info('magic result count $matchCount');
return results;
}
}

View File

@@ -180,7 +180,7 @@ class SearchService {
Future<List<GenericSearchResult>> getMagicSectionResults(
BuildContext context,
) async {
if (localSettings.isMLIndexingEnabled && flagService.internalUser) {
if (localSettings.isMLIndexingEnabled) {
return MagicCacheService.instance.getMagicGenericSearchResult(context);
} else {
return <GenericSearchResult>[];

View File

@@ -137,9 +137,11 @@ class _TextInputWidgetState extends State<TextInputWidget> {
if (executionState == ExecutionState.successful) {
Future.delayed(Duration(seconds: widget.popNavAfterSubmission ? 1 : 2),
() {
setState(() {
executionState = ExecutionState.idle;
});
if (mounted) {
setState(() {
executionState = ExecutionState.idle;
});
}
});
}
final colorScheme = getEnteColorScheme(context);

View File

@@ -7,9 +7,7 @@ import 'package:photo_manager/photo_manager.dart';
import "package:photos/generated/l10n.dart";
import 'package:photos/services/sync_service.dart';
import "package:photos/theme/ente_theme.dart";
import "package:photos/utils/debouncer.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/email_util.dart";
import "package:photos/utils/photo_manager_util.dart";
import "package:styled_text/styled_text.dart";
@@ -21,88 +19,67 @@ class GrantPermissionsWidget extends StatefulWidget {
}
class _GrantPermissionsWidgetState extends State<GrantPermissionsWidget> {
final _debouncer = Debouncer(const Duration(milliseconds: 500));
final Logger _logger = Logger("_GrantPermissionsWidgetState");
@override
void dispose() {
_debouncer.cancelDebounceTimer();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isLightMode = Theme.of(context).brightness == Brightness.light;
return Scaffold(
body: SingleChildScrollView(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleEnd: (details) {
_debouncer.run(() async {
unawaited(
triggerSendLogs(
"support@ente.io",
"Stuck on grant permission screen on ${Platform.operatingSystem}",
null,
),
);
});
},
child: Padding(
padding: const EdgeInsets.only(top: 20, bottom: 120),
child: Column(
children: [
const SizedBox(
height: 24,
),
Center(
child: Stack(
alignment: Alignment.center,
children: [
isLightMode
? Image.asset(
'assets/loading_photos_background.png',
color: Colors.white.withOpacity(0.4),
colorBlendMode: BlendMode.modulate,
)
: Image.asset(
'assets/loading_photos_background_dark.png',
),
Center(
child: Column(
children: [
const SizedBox(height: 42),
Image.asset(
"assets/gallery_locked.png",
height: 160,
),
],
),
child: Padding(
padding: const EdgeInsets.only(top: 20, bottom: 120),
child: Column(
children: [
const SizedBox(
height: 24,
),
Center(
child: Stack(
alignment: Alignment.center,
children: [
isLightMode
? Image.asset(
'assets/loading_photos_background.png',
color: Colors.white.withOpacity(0.4),
colorBlendMode: BlendMode.modulate,
)
: Image.asset(
'assets/loading_photos_background_dark.png',
),
Center(
child: Column(
children: [
const SizedBox(height: 42),
Image.asset(
"assets/gallery_locked.png",
height: 160,
),
],
),
],
),
),
],
),
const SizedBox(height: 36),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
child: StyledText(
text: S.of(context).entePhotosPerm,
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(fontWeight: FontWeight.w700),
tags: {
'i': StyledTextTag(
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(fontWeight: FontWeight.w400),
),
},
),
),
const SizedBox(height: 36),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
child: StyledText(
text: S.of(context).entePhotosPerm,
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(fontWeight: FontWeight.w700),
tags: {
'i': StyledTextTag(
style: Theme.of(context)
.textTheme
.headlineSmall!
.copyWith(fontWeight: FontWeight.w400),
),
},
),
],
),
),
],
),
),
),

View File

@@ -2,6 +2,7 @@ import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import "package:flutter/scheduler.dart";
import "package:flutter_animate/flutter_animate.dart";
import "package:photos/services/update_service.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import 'package:photos/utils/data_util.dart';
@@ -162,17 +163,30 @@ class _Price extends StatelessWidget {
final priceDouble = double.parse(priceWithoutCurrency);
final pricePerMonth = priceDouble / 12;
final pricePerMonthString = pricePerMonth.toStringAsFixed(2);
final bool isPlayStore = UpdateService.instance.isPlayStoreFlavor();
return Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
currencySymbol + pricePerMonthString + ' / ' + 'month',
style: textTheme.largeBold.copyWith(color: textBaseLight),
),
Text(
price + " / " + "yr",
style: textTheme.small.copyWith(color: textFaintLight),
),
if (isPlayStore)
Text(
price + " / " + "yr",
style: textTheme.largeBold.copyWith(color: textBaseLight),
),
if (isPlayStore)
Text(
currencySymbol + pricePerMonthString + ' / ' + 'month',
style: textTheme.small.copyWith(color: textFaintLight),
),
if (!isPlayStore)
Text(
currencySymbol + pricePerMonthString + ' / ' + 'month',
style: textTheme.largeBold.copyWith(color: textBaseLight),
),
if (!isPlayStore)
Text(
price + " / " + "yr",
style: textTheme.small.copyWith(color: textFaintLight),
),
],
)
.animate(delay: const Duration(milliseconds: 100))

View File

@@ -133,12 +133,13 @@ class CustomPinKeypad extends StatelessWidget {
}
}
class _Button extends StatelessWidget {
class _Button extends StatefulWidget {
final String number;
final String text;
final VoidCallback? onTap;
final bool muteButton;
final Widget? icon;
const _Button({
required this.number,
required this.text,
@@ -147,30 +148,59 @@ class _Button extends StatelessWidget {
this.icon,
});
@override
State<_Button> createState() => _ButtonState();
}
class _ButtonState extends State<_Button> {
bool isPressed = false;
void _onTapDown(TapDownDetails details) {
setState(() {
isPressed = true;
});
}
void _onTapUp(TapUpDetails details) async {
setState(() {
isPressed = false;
});
}
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
return Expanded(
child: GestureDetector(
onTap: onTap,
child: Container(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTap: widget.onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
curve: Curves.easeOut,
margin: const EdgeInsets.all(4),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(6),
color: muteButton
? colorScheme.fillFaintPressed
: icon == null
? colorScheme.backgroundElevated2
: null,
color: isPressed
? colorScheme.backgroundElevated
: widget.muteButton
? colorScheme.fillFaintPressed
: widget.icon == null
? colorScheme.backgroundElevated2
: null,
),
child: Center(
child: muteButton
child: widget.muteButton
? const SizedBox.shrink()
: icon != null
: widget.icon != null
? Container(
child: icon,
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 10,
),
child: widget.icon,
)
: Container(
padding: const EdgeInsets.all(4),
@@ -178,11 +208,11 @@ class _Button extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
number,
widget.number,
style: textTheme.h3,
),
Text(
text,
widget.text,
style: textTheme.tinyBold,
),
],

View File

@@ -1,6 +1,7 @@
import "dart:async";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/files_updated_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
@@ -44,6 +45,8 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
late final List<EnteFile> files;
late final StreamSubscription<LocalPhotosUpdatedEvent> _filesUpdatedEvent;
late final StreamSubscription<MagicSortChangeEvent> _magicSortChangeEvent;
late final Logger _logger = Logger("_MagicResultScreenState");
late final Map<int, int> fileIDToRelevantPos;
bool _enableGrouping = false;
@override
@@ -51,6 +54,7 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
super.initState();
files = widget.files;
_enableGrouping = widget.enableGrouping;
fileIDToRelevantPos = getFileIDToRelevantPos();
_filesUpdatedEvent =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
if (event.type == EventType.deletedFromDevice ||
@@ -68,11 +72,17 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
Bus.instance.on<MagicSortChangeEvent>().listen((event) {
if (event.sortType == MagicSortType.mostRelevant) {
if (_enableGrouping) {
files.sort(
(a, b) =>
widget.fileIdToPosMap[a.uploadedFileID]! -
widget.fileIdToPosMap[b.uploadedFileID]!,
);
if (fileIDToRelevantPos.isNotEmpty) {
files.sort(
(a, b) =>
fileIDToRelevantPos[a.uploadedFileID]! -
fileIDToRelevantPos[b.uploadedFileID]!,
);
} else {
_logger.warning(
"fileIdToPosMap is empty, cannot sort by most relevant.",
);
}
}
setState(() {
_enableGrouping = false;
@@ -88,6 +98,26 @@ class _MagicResultScreenState extends State<MagicResultScreen> {
});
}
Map<int, int> getFileIDToRelevantPos() {
if (widget.fileIdToPosMap.isNotEmpty) {
return widget.fileIdToPosMap;
} else if (widget.enableGrouping == false) {
_logger.warning(
"fileIdToPosMap is empty, assuming existing list of files is sorted by most relevant.",
);
final map = <int, int>{};
for (int i = 0; i < files.length; i++) {
map[files[i].uploadedFileID!] = i;
}
return map;
} else {
_logger.warning(
"fileIdToPosMap is empty, cannot sort by most relevant.",
);
return <int, int>{};
}
}
@override
void dispose() {
_filesUpdatedEvent.cancel();

View File

@@ -61,13 +61,15 @@ class SearchWidgetState extends State<SearchWidget> {
//This buffer is for doing this operation only after SearchWidget's
//animation is complete.
Future.delayed(const Duration(milliseconds: 300), () {
final RenderBox box =
widgetKey.currentContext!.findRenderObject() as RenderBox;
final heightOfWidget = box.size.height;
final offsetPosition = box.localToGlobal(Offset.zero);
final y = offsetPosition.dy;
final heightOfScreen = MediaQuery.sizeOf(context).height;
_distanceOfWidgetFromBottom = heightOfScreen - (y + heightOfWidget);
if (mounted) {
final RenderBox box =
widgetKey.currentContext!.findRenderObject() as RenderBox;
final heightOfWidget = box.size.height;
final offsetPosition = box.localToGlobal(Offset.zero);
final y = offsetPosition.dy;
final heightOfScreen = MediaQuery.sizeOf(context).height;
_distanceOfWidgetFromBottom = heightOfScreen - (y + heightOfWidget);
}
});
textController.addListener(textControllerListener);

View File

@@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.9.41+941
version: 0.9.44+944
publish_to: none
environment:

View File

@@ -893,14 +893,14 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
}
})
schedule(c, "@every 2m", func() {
schedule(c, "@every 10m", func() {
fileController.CleanupDeletedFiles()
})
schedule(c, "@every 101s", func() {
embeddingCtrl.CleanupDeletedEmbeddings()
})
schedule(c, "@every 10m", func() {
schedule(c, "@every 17m", func() {
trashController.DropFileMetadataCron()
})
@@ -926,7 +926,7 @@ func setupAndStartCrons(userAuthRepo *repo.UserAuthRepository, publicCollectionR
trashController.ProcessEmptyTrashRequests()
})
schedule(c, "@every 30m", func() {
schedule(c, "@every 45m", func() {
// delete unclaimed codes older than 60 minutes
_ = castDb.DeleteUnclaimedCodes(context.Background(), timeUtil.MicrosecondsBeforeMinutes(60))
dataCleanupCtrl.DeleteDataCron()

View File

@@ -30,7 +30,7 @@ services:
command: "TCP-LISTEN:3200,fork,reuseaddr TCP:minio:3200"
postgres:
image: postgres:12
image: postgres:15
ports:
- 5432:5432
environment:

View File

@@ -178,13 +178,10 @@ func (c *Controller) RevokeInvite(ctx context.Context, adminID int64, id uuid.UU
}
func (c *Controller) CloseFamily(ctx context.Context, adminID int64) error {
familyMembers, err := c.FamilyRepo.GetMembersWithStatus(adminID, repo.ActiveFamilyMemberStatus)
logger := logrus.WithField("adminID", adminID).WithField("operation", "CloseFamily")
err := c.removeMembers(ctx, adminID, logger)
if err != nil {
return stacktrace.Propagate(err, "")
}
if len(familyMembers) != 1 {
msg := fmt.Sprintf("can not close family with %d members", len(familyMembers))
return stacktrace.Propagate(ente.NewBadRequestWithMessage(msg), "")
return stacktrace.Propagate(err, "failed to remove members")
}
err = c.FamilyRepo.CloseFamily(ctx, adminID)
if err != nil {

View File

@@ -112,29 +112,37 @@ func (c *Controller) HandleAccountDeletion(ctx context.Context, userID int64, lo
}
} else {
logger.Info("user is a family admin, revoking invites & removing members")
members, err := c.FetchMembersForAdminID(ctx, userID)
if err != nil {
return stacktrace.Propagate(err, "")
}
for _, member := range members.Members {
if member.IsAdmin {
continue
} else if member.Status == ente.ACCEPTED {
logger.Info(fmt.Sprintf("removing memeber_id %d", member.MemberUserID))
err = c.RemoveMember(ctx, userID, member.ID)
if err != nil {
return stacktrace.Propagate(err, "")
}
} else if member.Status == ente.INVITED {
logger.Info(fmt.Sprintf("revoking invite member_id %d", member.MemberUserID))
err = c.RevokeInvite(ctx, userID, member.ID)
if err != nil {
return stacktrace.Propagate(err, "")
}
} else {
logger.WithField("member", member).Error("unxpected state during account deletion")
}
removeErr := c.removeMembers(ctx, userID, logger)
if removeErr != nil {
return removeErr
}
}
return nil
}
func (c *Controller) removeMembers(ctx context.Context, adminID int64, logger *logrus.Entry) error {
members, err := c.FetchMembersForAdminID(ctx, adminID)
if err != nil {
return stacktrace.Propagate(err, "")
}
for _, member := range members.Members {
if member.IsAdmin {
continue
} else if member.Status == ente.ACCEPTED {
logger.Info(fmt.Sprintf("removing memeber_id %d", member.MemberUserID))
err = c.RemoveMember(ctx, adminID, member.ID)
if err != nil {
return stacktrace.Propagate(err, "")
}
} else if member.Status == ente.INVITED {
logger.Info(fmt.Sprintf("revoking invite member_id %d", member.MemberUserID))
err = c.RevokeInvite(ctx, adminID, member.ID)
if err != nil {
return stacktrace.Propagate(err, "")
}
} else {
logger.WithField("member", member).Error("unxpected state during account deletion")
}
}
return nil

View File

@@ -689,7 +689,7 @@ func (c *FileController) CleanupDeletedFiles() {
defer func() {
c.LockController.ReleaseLock(DeletedObjectQueueLock)
}()
items, err := c.QueueRepo.GetItemsReadyForDeletion(repo.DeleteObjectQueue, 2000)
items, err := c.QueueRepo.GetItemsReadyForDeletion(repo.DeleteObjectQueue, 1000)
if err != nil {
log.WithError(err).Error("Failed to fetch items from queue")
return

View File

@@ -43,7 +43,7 @@ func (c *Controller) delete(i int) {
if err != nil {
// Sleep in proportion to the (arbitrary) index to space out the
// workers further.
time.Sleep(time.Duration(i+1) * time.Minute)
time.Sleep(time.Duration(i+5) * time.Minute)
}
}
}

View File

@@ -46,7 +46,7 @@ const Page: React.FC = () => {
const showPasskeyFetchFailedErrorDialog = useCallback(() => {
setDialogBoxAttributesV2({
title: t("ERROR"),
title: t("error"),
content: t("passkey_fetch_failed"),
close: {},
});
@@ -392,7 +392,7 @@ const RenamePasskeyDialog: React.FC<RenamePasskeyDialogProps> = ({
initialValue={passkey.friendlyName}
callback={handleSubmit}
placeholder={t("enter_passkey_name")}
buttonText={t("RENAME")}
buttonText={t("rename")}
fieldType="text"
secondaryButtonAction={onClose}
submitButtonProps={{ sx: { mt: 1, mb: 0 } }}

View File

@@ -130,7 +130,7 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const somethingWentWrong = () =>
setDialogBoxAttributesV2({
title: t("ERROR"),
title: t("error"),
close: { variant: "critical" },
content: t("UNKNOWN_ERROR"),
});

View File

@@ -407,7 +407,7 @@ const Footer: React.FC = () => {
href="https://github.com/ente-io/ente/tree/main/auth#-download"
download
>
<Button color="accent">{t("DOWNLOAD")}</Button>
<Button color="accent">{t("download")}</Button>
</a>
</Footer_>
);

View File

@@ -6,7 +6,7 @@ import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { readCastData, storeCastData } from "services/cast-data";
import { getCastData, register } from "services/pair";
import { advertiseOnChromecast } from "../services/chromecast";
import { advertiseOnChromecast } from "../services/chromecast-receiver";
export default function Index() {
const [publicKeyB64, setPublicKeyB64] = useState<string | undefined>();

View File

@@ -5,7 +5,7 @@ import { FilledCircleCheck } from "components/FilledCircleCheck";
import { useRouter } from "next/router";
import React, { useEffect, useState } from "react";
import { readCastData } from "services/cast-data";
import { isChromecast } from "services/chromecast";
import { isChromecast } from "services/chromecast-receiver";
import { imageURLGenerator } from "services/render";
export default function Slideshow() {

View File

@@ -28,7 +28,7 @@ import HTTPService from "@ente/shared/network/HTTPService";
import type { AxiosResponse } from "axios";
import type { CastData } from "services/cast-data";
import { detectMediaMIMEType } from "services/detect-type";
import { isChromecast } from "./chromecast";
import { isChromecast } from "./chromecast-receiver";
/**
* An async generator function that loops through all the files in the

View File

@@ -17,7 +17,6 @@
"exifreader": "^4",
"fast-srp-hap": "^2.0.4",
"ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm",
"hdbscan": "0.0.1-alpha.5",
"leaflet": "^1.9.4",
"leaflet-defaulticon-compatibility": "^0.1.1",
"localforage": "^1.9.0",
@@ -32,7 +31,7 @@
"react-select": "^5.8.0",
"react-top-loading-bar": "^2.0.1",
"react-virtualized-auto-sizer": "^1.0",
"react-window": "^1.8.6",
"react-window": "^1.8.10",
"sanitize-filename": "^1.6.3",
"similarity-transformation": "^0.0.1",
"transformation-matrix": "^2.16",
@@ -47,8 +46,7 @@
"@types/leaflet": "^1.9",
"@types/photoswipe": "^4.1.1",
"@types/react-virtualized-auto-sizer": "^1.0",
"@types/react-window": "^1.8.2",
"@types/react-window-infinite-loader": "^1.0.3",
"@types/react-window": "^1.8.8",
"@types/uuid": "^9.0.2",
"@types/zxcvbn": "^4.4.1"
}

View File

@@ -29,7 +29,7 @@ export default function AuthenticateUserModal({
const somethingWentWrong = () =>
setDialogMessage({
title: t("ERROR"),
title: t("error"),
close: { variant: "critical" },
content: t("UNKNOWN_ERROR"),
});

View File

@@ -0,0 +1,218 @@
import { boxSeal } from "@/base/crypto/libsodium";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { loadCast } from "@/new/photos/utils/chromecast-sender";
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import SingleInputForm, {
type SingleInputFormProps,
} from "@ente/shared/components/SingleInputForm";
import castGateway from "@ente/shared/network/cast";
import { Button, Link, Stack, Typography } from "@mui/material";
import { t } from "i18next";
import { useEffect, useState } from "react";
import { Trans } from "react-i18next";
import { v4 as uuidv4 } from "uuid";
interface AlbumCastDialogProps {
/** If `true`, the dialog is shown. */
open: boolean;
/** Callback fired when the dialog wants to be closed. */
onClose: () => void;
/** The collection that we want to cast. */
collection: Collection;
}
/**
* A dialog that shows various options that the user has for casting an album.
*/
export const AlbumCastDialog: React.FC<AlbumCastDialogProps> = ({
open,
onClose,
collection,
}) => {
const [view, setView] = useState<
"choose" | "auto" | "pin" | "auto-cast-error"
>("choose");
const [browserCanCast, setBrowserCanCast] = useState(false);
// Make API call to clear all previous sessions on component mount.
useEffect(() => {
castGateway.revokeAllTokens();
setBrowserCanCast(typeof window["chrome"] !== "undefined");
}, []);
const onSubmit: SingleInputFormProps["callback"] = async (
value,
setFieldError,
) => {
try {
await doCast(value.trim());
onClose();
} catch (e) {
if (e instanceof Error && e.message == "tv-not-found") {
setFieldError(t("tv_not_found"));
} else {
setFieldError(t("UNKNOWN_ERROR"));
}
}
};
const doCast = async (pin: string) => {
// Does the TV exist? have they advertised their existence?
const tvPublicKeyB64 = await castGateway.getPublicKey(pin);
if (!tvPublicKeyB64) {
throw new Error("tv-not-found");
}
// Generate random id.
const castToken = uuidv4();
// Ok, they exist. let's give them the good stuff.
const payload = JSON.stringify({
castToken: castToken,
collectionID: collection.id,
collectionKey: collection.key,
});
const encryptedPayload = await boxSeal(btoa(payload), tvPublicKeyB64);
// Hey TV, we acknowlege you!
await castGateway.publishCastPayload(
pin,
encryptedPayload,
collection.id,
castToken,
);
};
useEffect(() => {
if (view === "auto") {
loadCast().then(async (cast) => {
const instance = cast.framework.CastContext.getInstance();
try {
await instance.requestSession();
} catch (e) {
setView("auto-cast-error");
log.error("Error requesting session", e);
return;
}
const session = instance.getCurrentSession();
session.addMessageListener(
"urn:x-cast:pair-request",
(_, message) => {
const data = message;
const obj = JSON.parse(data);
const code = obj.code;
if (code) {
doCast(code)
.then(() => {
setView("choose");
onClose();
})
.catch((e) => {
log.error("Error casting to TV", e);
setView("auto-cast-error");
});
}
},
);
const collectionID = collection.id;
session
.sendMessage("urn:x-cast:pair-request", { collectionID })
.then(() => {
log.debug(() => "urn:x-cast:pair-request sent");
});
});
}
}, [view]);
useEffect(() => {
if (open) castGateway.revokeAllTokens();
}, [open]);
return (
<DialogBoxV2
open={open}
onClose={onClose}
attributes={{ title: t("cast_album_to_tv") }}
sx={{ zIndex: 1600 }}
>
{view == "choose" && (
<Stack sx={{ py: 1, gap: 4 }}>
{browserCanCast && (
<Stack sx={{ gap: 2 }}>
<Typography color={"text.muted"}>
{t("cast_auto_pair_description")}
</Typography>
<Button onClick={() => setView("auto")}>
{t("cast_auto_pair")}
</Button>
</Stack>
)}
<Stack sx={{ gap: 2 }}>
<Typography color="text.muted">
{t("pair_with_pin_description")}
</Typography>
<Button onClick={() => setView("pin")}>
{t("pair_with_pin")}
</Button>
</Stack>
</Stack>
)}
{view == "auto" && (
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
<div>
<EnteSpinner />
</div>
<Typography>{t("choose_device_from_browser")}</Typography>
<Button color="secondary" onClick={() => setView("choose")}>
{t("GO_BACK")}
</Button>
</Stack>
)}
{view == "auto-cast-error" && (
<Stack sx={{ pt: 1, gap: 3, textAlign: "center" }}>
<Typography>{t("cast_auto_pair_failed")}</Typography>
<Button color="secondary" onClick={() => setView("choose")}>
{t("GO_BACK")}
</Button>
</Stack>
)}
{view == "pin" && (
<>
<Typography>
<Trans
i18nKey="visit_cast_url"
components={{
a: (
<Link
target="_blank"
href="https://cast.ente.io"
/>
),
}}
values={{ url: "cast.ente.io" }}
/>
</Typography>
<Typography>{t("enter_cast_pin_code")}</Typography>
<SingleInputForm
callback={onSubmit}
fieldType="text"
realLabel={"Code"}
realPlaceholder={"123456"}
buttonText={t("pair_device_to_tv")}
submitButtonProps={{ sx: { mt: 1, mb: 2 } }}
/>
<Button variant="text" onClick={() => setView("choose")}>
{t("GO_BACK")}
</Button>
</>
)}
</DialogBoxV2>
);
};

View File

@@ -1,8 +1,9 @@
import { AllCollectionTile } from "@/new/photos/components/ItemCards";
import type { CollectionSummary } from "@/new/photos/types/collection";
import { Typography } from "@mui/material";
import { t } from "i18next";
import { CollectionSummary } from "types/collection";
import CollectionCard from "../CollectionCard";
import { AllCollectionTile, AllCollectionTileText } from "../styledComponents";
import { AllCollectionTileText } from "../styledComponents";
interface Iprops {
collectionSummary: CollectionSummary;

View File

@@ -1,3 +1,4 @@
import type { CollectionSummary } from "@/new/photos/types/collection";
import { FlexWrapper } from "@ente/shared/components/Container";
import useWindowSize from "@ente/shared/hooks/useWindowSize";
import { DialogContent } from "@mui/material";
@@ -8,7 +9,6 @@ import {
ListChildComponentProps,
areEqual,
} from "react-window";
import { CollectionSummary } from "types/collection";
import AllCollectionCard from "./collectionCard";
import { AllCollectionMobileBreakpoint } from "./dialog";

View File

@@ -1,46 +0,0 @@
import {
FlexWrapper,
FluidContainer,
IconButtonWithBG,
} from "@ente/shared/components/Container";
import Close from "@mui/icons-material/Close";
import { Box, DialogTitle, Stack, Typography } from "@mui/material";
import CollectionListSortBy from "components/Collections/CollectionListSortBy";
import { t } from "i18next";
export default function AllCollectionsHeader({
onClose,
collectionCount,
collectionSortBy,
setCollectionSortBy,
isInHiddenSection,
}) {
return (
<DialogTitle>
<FlexWrapper>
<FluidContainer mr={1.5}>
<Box>
<Typography variant="h3">
{isInHiddenSection
? t("ALL_HIDDEN_ALBUMS")
: t("ALL_ALBUMS")}
</Typography>
<Typography variant="small" color={"text.muted"}>
{t("albums_count", { count: collectionCount })}
</Typography>
</Box>
</FluidContainer>
<Stack direction="row" spacing={1.5}>
<CollectionListSortBy
activeSortBy={collectionSortBy}
setSortBy={setCollectionSortBy}
nestedInDialog
/>
<IconButtonWithBG onClick={onClose}>
<Close />
</IconButtonWithBG>
</Stack>
</FlexWrapper>
</DialogTitle>
);
}

View File

@@ -1,39 +1,50 @@
import { Divider, useMediaQuery } from "@mui/material";
import { CollectionsSortOptions } from "@/new/photos/components/CollectionsSortOptions";
import { FilledIconButton } from "@/new/photos/components/mui-custom";
import type { CollectionSummary } from "@/new/photos/types/collection";
import { CollectionsSortBy } from "@/new/photos/types/collection";
import { FlexWrapper, FluidContainer } from "@ente/shared/components/Container";
import Close from "@mui/icons-material/Close";
import {
Box,
DialogTitle,
Divider,
Stack,
Typography,
useMediaQuery,
} from "@mui/material";
import {
AllCollectionDialog,
Transition,
} from "components/Collections/AllCollections/dialog";
import { CollectionSummary } from "types/collection";
import { COLLECTION_LIST_SORT_BY } from "utils/collection";
import { t } from "i18next";
import AllCollectionContent from "./content";
import AllCollectionsHeader from "./header";
interface Iprops {
interface AllCollectionsProps {
open: boolean;
onClose: () => void;
collectionSummaries: CollectionSummary[];
setActiveCollectionID: (id?: number) => void;
collectionListSortBy: COLLECTION_LIST_SORT_BY;
setCollectionListSortBy: (v: COLLECTION_LIST_SORT_BY) => void;
onSelectCollectionID: (id: number) => void;
collectionsSortBy: CollectionsSortBy;
onChangeCollectionsSortBy: (by: CollectionsSortBy) => void;
isInHiddenSection: boolean;
}
const LeftSlideTransition = Transition("up");
export default function AllCollections(props: Iprops) {
export default function AllCollections(props: AllCollectionsProps) {
const {
collectionSummaries,
open,
onClose,
setActiveCollectionID,
collectionListSortBy,
setCollectionListSortBy,
onSelectCollectionID,
collectionsSortBy,
onChangeCollectionsSortBy,
isInHiddenSection,
} = props;
const isMobile = useMediaQuery("(max-width: 428px)");
const onCollectionClick = (collectionID: number) => {
setActiveCollectionID(collectionID);
onSelectCollectionID(collectionID);
onClose();
};
@@ -47,11 +58,13 @@ export default function AllCollections(props: Iprops) {
fullWidth={true}
>
<AllCollectionsHeader
isInHiddenSection={isInHiddenSection}
onClose={onClose}
{...{
isInHiddenSection,
onClose,
collectionsSortBy,
onChangeCollectionsSortBy,
}}
collectionCount={props.collectionSummaries.length}
collectionSortBy={collectionListSortBy}
setCollectionSortBy={setCollectionListSortBy}
/>
<Divider />
<AllCollectionContent
@@ -61,3 +74,38 @@ export default function AllCollections(props: Iprops) {
</AllCollectionDialog>
);
}
const AllCollectionsHeader = ({
onClose,
collectionCount,
collectionsSortBy,
onChangeCollectionsSortBy,
isInHiddenSection,
}) => (
<DialogTitle>
<FlexWrapper>
<FluidContainer mr={1.5}>
<Box>
<Typography variant="h3">
{isInHiddenSection
? t("all_hidden_albums")
: t("all_albums")}
</Typography>
<Typography variant="small" color={"text.muted"}>
{t("albums_count", { count: collectionCount })}
</Typography>
</Box>
</FluidContainer>
<Stack direction="row" spacing={1.5}>
<CollectionsSortOptions
activeSortBy={collectionsSortBy}
onChangeSortBy={onChangeCollectionsSortBy}
nestedInDialog
/>
<FilledIconButton onClick={onClose}>
<Close />
</FilledIconButton>
</Stack>
</FlexWrapper>
</DialogTitle>
);

View File

@@ -6,7 +6,7 @@ import downloadManager from "@/new/photos/services/download";
import { EnteFile } from "@/new/photos/types/file";
import { useEffect, useState } from "react";
/** See also: {@link ItemCard}. */
/** Deprecated in favor of {@link ItemCard}. */
export default function CollectionCard(props: {
children?: any;
coverFile: EnteFile;

View File

@@ -0,0 +1,767 @@
import { assertionFailed } from "@/base/assert";
import log from "@/base/log";
import type { Collection } from "@/media/collection";
import { ItemVisibility } from "@/media/file-metadata";
import {
GalleryItemsHeaderAdapter,
GalleryItemsSummary,
} from "@/new/photos/components/Gallery/ListHeader";
import { SpaceBetweenBox } from "@/new/photos/components/mui-custom";
import type {
CollectionSummary,
CollectionSummaryType,
} from "@/new/photos/types/collection";
import { HorizontalFlex } from "@ente/shared/components/Container";
import EnteSpinner from "@ente/shared/components/EnteSpinner";
import OverflowMenu, {
StyledMenu,
} from "@ente/shared/components/OverflowMenu/menu";
import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option";
import ArchiveOutlined from "@mui/icons-material/ArchiveOutlined";
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
import EditIcon from "@mui/icons-material/Edit";
import Favorite from "@mui/icons-material/FavoriteRounded";
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
import LinkIcon from "@mui/icons-material/Link";
import LogoutIcon from "@mui/icons-material/Logout";
import MoreHoriz from "@mui/icons-material/MoreHoriz";
import PeopleIcon from "@mui/icons-material/People";
import PushPinOutlined from "@mui/icons-material/PushPinOutlined";
import SortIcon from "@mui/icons-material/Sort";
import TvIcon from "@mui/icons-material/Tv";
import Unarchive from "@mui/icons-material/Unarchive";
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined";
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
import { Box, IconButton, Stack, Tooltip } from "@mui/material";
import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer";
import { UnPinIcon } from "components/icons/UnPinIcon";
import { t } from "i18next";
import { AppContext } from "pages/_app";
import { GalleryContext } from "pages/gallery";
import React, { useCallback, useContext, useRef, useState } from "react";
import { Trans } from "react-i18next";
import * as CollectionAPI from "services/collectionService";
import * as TrashService from "services/trashService";
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import {
ALL_SECTION,
changeCollectionOrder,
changeCollectionSortOrder,
changeCollectionVisibility,
downloadCollectionHelper,
downloadDefaultHiddenCollectionHelper,
HIDDEN_ITEMS_SECTION,
isHiddenCollection,
} from "utils/collection";
import { isArchivedCollection, isPinnedCollection } from "utils/magicMetadata";
interface CollectionHeaderProps {
collectionSummary: CollectionSummary;
activeCollection: Collection;
setActiveCollectionID: (collectionID: number) => void;
isActiveCollectionDownloadInProgress: () => boolean;
onCollectionShare: () => void;
onCollectionCast: () => void;
setCollectionNamerAttributes: SetCollectionNamerAttributes;
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
}
/**
* A header shown at the top of the list of photos in the gallery, when the
* gallery is showing a collection.
*/
export const CollectionHeader: React.FC<CollectionHeaderProps> = ({
collectionSummary,
...rest
}) => {
if (!collectionSummary) {
assertionFailed("Gallery/CollectionHeader without a collection");
return <></>;
}
const { name, type, fileCount } = collectionSummary;
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
switch (type) {
case "favorites":
return <Favorite />;
case "archived":
return <ArchiveOutlined />;
case "incomingShareViewer":
case "incomingShareCollaborator":
return <PeopleIcon />;
case "outgoingShare":
return <PeopleIcon />;
case "sharedOnlyViaLink":
return <LinkIcon />;
default:
return <></>;
}
};
return (
<GalleryItemsHeaderAdapter>
<SpaceBetweenBox>
<GalleryItemsSummary
name={name}
fileCount={fileCount}
endIcon={<EndIcon type={type} />}
/>
{shouldShowOptions(type) && (
<CollectionOptions collectionSummaryType={type} {...rest} />
)}
</SpaceBetweenBox>
</GalleryItemsHeaderAdapter>
);
};
const shouldShowOptions = (type: CollectionSummaryType) =>
type != "all" && type != "archive";
type CollectionOptionsProps = Omit<
CollectionHeaderProps,
"collectionSummary"
> & {
collectionSummaryType: CollectionSummaryType;
};
const CollectionOptions: React.FC<CollectionOptionsProps> = ({
activeCollection,
collectionSummaryType,
setActiveCollectionID,
onCollectionShare,
onCollectionCast,
setCollectionNamerAttributes,
setFilesDownloadProgressAttributesCreator,
isActiveCollectionDownloadInProgress,
}) => {
const { startLoading, finishLoading, setDialogMessage } =
useContext(AppContext);
const { syncWithRemote } = useContext(GalleryContext);
const overFlowMenuIconRef = useRef<SVGSVGElement>(null);
const [openSortOrderMenu, setOpenSortOrderMenu] = useState(false);
const handleError = useCallback(
(e: unknown) => {
log.error("Collection action failed", e);
setDialogMessage({
title: t("error"),
content: t("UNKNOWN_ERROR"),
close: { variant: "critical" },
});
},
[setDialogMessage],
);
/**
* Return a new function by wrapping an async function in an error handler,
* showing the global loading bar when the function runs, and syncing with
* remote on completion.
*/
const wrap = useCallback(
(f: () => Promise<void>) => {
const wrapped = async () => {
startLoading();
try {
await f();
} catch (e) {
handleError(e);
} finally {
void syncWithRemote(false, true);
finishLoading();
}
};
return (): void => void wrapped();
},
[handleError, syncWithRemote, startLoading, finishLoading],
);
const showRenameCollectionModal = () => {
setCollectionNamerAttributes({
title: t("rename_album"),
buttonText: t("rename"),
autoFilledName: activeCollection.name,
callback: renameCollection,
});
};
const _renameCollection = async (newName: string) => {
if (activeCollection.name !== newName) {
await CollectionAPI.renameCollection(activeCollection, newName);
}
};
const renameCollection = (newName: string) =>
wrap(() => _renameCollection(newName))();
const confirmDeleteCollection = () => {
setDialogMessage({
title: t("delete_album_title"),
content: (
<Trans
i18nKey={"delete_album_message"}
components={{
a: <Box component={"span"} color="text.base" />,
}}
/>
),
proceed: {
text: t("delete_photos"),
action: deleteCollectionAlongWithFiles,
variant: "critical",
},
secondary: {
text: t("keep_photos"),
action: deleteCollectionButKeepFiles,
variant: "primary",
},
close: {
text: t("cancel"),
},
});
};
const deleteCollectionAlongWithFiles = wrap(async () => {
await CollectionAPI.deleteCollection(activeCollection.id, false);
setActiveCollectionID(ALL_SECTION);
});
const deleteCollectionButKeepFiles = wrap(async () => {
await CollectionAPI.deleteCollection(activeCollection.id, true);
setActiveCollectionID(ALL_SECTION);
});
const confirmEmptyTrash = () =>
setDialogMessage({
title: t("empty_trash_title"),
content: t("empty_trash_message"),
proceed: {
action: emptyTrash,
text: t("empty_trash"),
variant: "critical",
},
close: { text: t("cancel") },
});
const emptyTrash = wrap(async () => {
await TrashService.emptyTrash();
await TrashService.clearLocalTrash();
setActiveCollectionID(ALL_SECTION);
});
const _downloadCollection = () => {
if (isActiveCollectionDownloadInProgress()) return;
if (collectionSummaryType == "hiddenItems") {
return downloadDefaultHiddenCollectionHelper(
setFilesDownloadProgressAttributesCreator(
activeCollection.name,
HIDDEN_ITEMS_SECTION,
true,
),
);
} else {
return downloadCollectionHelper(
activeCollection.id,
setFilesDownloadProgressAttributesCreator(
activeCollection.name,
activeCollection.id,
isHiddenCollection(activeCollection),
),
);
}
};
const downloadCollection = () =>
void _downloadCollection().catch(handleError);
const archiveAlbum = wrap(() =>
changeCollectionVisibility(activeCollection, ItemVisibility.archived),
);
const unarchiveAlbum = wrap(() =>
changeCollectionVisibility(activeCollection, ItemVisibility.visible),
);
const confirmLeaveSharedAlbum = () => {
setDialogMessage({
title: t("leave_shared_album_title"),
content: t("leave_shared_album_message"),
proceed: {
text: t("leave_shared_album"),
action: leaveSharedAlbum,
variant: "critical",
},
close: {
text: t("cancel"),
},
});
};
const leaveSharedAlbum = wrap(async () => {
await CollectionAPI.leaveSharedAlbum(activeCollection.id);
setActiveCollectionID(ALL_SECTION);
});
const pinAlbum = wrap(() => changeCollectionOrder(activeCollection, 1));
const unpinAlbum = wrap(() => changeCollectionOrder(activeCollection, 0));
const hideAlbum = wrap(async () => {
await changeCollectionVisibility(
activeCollection,
ItemVisibility.hidden,
);
setActiveCollectionID(ALL_SECTION);
});
const unhideAlbum = wrap(async () => {
await changeCollectionVisibility(
activeCollection,
ItemVisibility.visible,
);
setActiveCollectionID(HIDDEN_ITEMS_SECTION);
});
const showSortOrderMenu = () => setOpenSortOrderMenu(true);
const closeSortOrderMenu = () => setOpenSortOrderMenu(false);
const changeSortOrderAsc = wrap(() =>
changeCollectionSortOrder(activeCollection, true),
);
const changeSortOrderDesc = wrap(() =>
changeCollectionSortOrder(activeCollection, false),
);
return (
<HorizontalFlex sx={{ display: "inline-flex", gap: "16px" }}>
<QuickOptions
collectionSummaryType={collectionSummaryType}
isDownloadInProgress={isActiveCollectionDownloadInProgress}
onEmptyTrashClick={confirmEmptyTrash}
onDownloadClick={downloadCollection}
onShareClick={onCollectionShare}
/>
<OverflowMenu
ariaControls={"collection-options"}
triggerButtonIcon={<MoreHoriz ref={overFlowMenuIconRef} />}
>
{collectionSummaryType == "trash" ? (
<EmptyTrashOption onClick={confirmEmptyTrash} />
) : collectionSummaryType == "favorites" ? (
<DownloadOption
isDownloadInProgress={
isActiveCollectionDownloadInProgress
}
onClick={downloadCollection}
>
{t("download_favorites")}
</DownloadOption>
) : collectionSummaryType == "uncategorized" ? (
<DownloadOption onClick={downloadCollection}>
{t("download_uncategorized")}
</DownloadOption>
) : collectionSummaryType == "hiddenItems" ? (
<DownloadOption onClick={downloadCollection}>
{t("download_hidden_items")}
</DownloadOption>
) : collectionSummaryType == "incomingShareViewer" ||
collectionSummaryType == "incomingShareCollaborator" ? (
<SharedCollectionOptions
isArchived={isArchivedCollection(activeCollection)}
onArchiveClick={archiveAlbum}
onUnarchiveClick={unarchiveAlbum}
onLeaveSharedAlbumClick={confirmLeaveSharedAlbum}
onCastClick={onCollectionCast}
/>
) : (
<AlbumCollectionOptions
isArchived={isArchivedCollection(activeCollection)}
isHidden={isHiddenCollection(activeCollection)}
isPinned={isPinnedCollection(activeCollection)}
onRenameClick={showRenameCollectionModal}
onSortClick={showSortOrderMenu}
onArchiveClick={archiveAlbum}
onUnarchiveClick={unarchiveAlbum}
onPinClick={pinAlbum}
onUnpinClick={unpinAlbum}
onHideClick={hideAlbum}
onUnhideClick={unhideAlbum}
onDeleteClick={confirmDeleteCollection}
onShareClick={onCollectionShare}
onCastClick={onCollectionCast}
/>
)}
</OverflowMenu>
<CollectionSortOrderMenu
open={openSortOrderMenu}
onClose={closeSortOrderMenu}
overFlowMenuIconRef={overFlowMenuIconRef}
onAscClick={changeSortOrderAsc}
onDescClick={changeSortOrderDesc}
/>
</HorizontalFlex>
);
};
/** Props for a generic option. */
interface OptionProps {
onClick: () => void;
}
interface QuickOptionsProps {
collectionSummaryType: CollectionSummaryType;
isDownloadInProgress: () => boolean;
onEmptyTrashClick: () => void;
onDownloadClick: () => void;
onShareClick: () => void;
}
const QuickOptions: React.FC<QuickOptionsProps> = ({
onEmptyTrashClick,
onDownloadClick,
onShareClick,
collectionSummaryType: type,
isDownloadInProgress,
}) => (
<Stack direction="row" sx={{ alignItems: "center", gap: "16px" }}>
{showEmptyTrashQuickOption(type) && (
<EmptyTrashQuickOption onClick={onEmptyTrashClick} />
)}
{showDownloadQuickOption(type) &&
(isDownloadInProgress() ? (
<EnteSpinner size="20px" sx={{ m: "12px" }} />
) : (
<DownloadQuickOption
onClick={onDownloadClick}
collectionSummaryType={type}
/>
))}
{showShareQuickOption(type) && (
<ShareQuickOption
onClick={onShareClick}
collectionSummaryType={type}
/>
)}
</Stack>
);
const showEmptyTrashQuickOption = (type: CollectionSummaryType) =>
type == "trash";
const EmptyTrashQuickOption: React.FC<OptionProps> = ({ onClick }) => (
<Tooltip title={t("empty_trash")}>
<IconButton onClick={onClick}>
<DeleteOutlinedIcon />
</IconButton>
</Tooltip>
);
const showDownloadQuickOption = (type: CollectionSummaryType) =>
type == "folder" ||
type == "favorites" ||
type == "album" ||
type == "uncategorized" ||
type == "hiddenItems" ||
type == "incomingShareViewer" ||
type == "incomingShareCollaborator" ||
type == "outgoingShare" ||
type == "sharedOnlyViaLink" ||
type == "archived" ||
type == "pinned";
type DownloadQuickOptionProps = OptionProps & {
collectionSummaryType: CollectionSummaryType;
};
const DownloadQuickOption: React.FC<DownloadQuickOptionProps> = ({
onClick,
collectionSummaryType,
}) => (
<Tooltip
title={
collectionSummaryType == "favorites"
? t("download_favorites")
: collectionSummaryType == "uncategorized"
? t("download_uncategorized")
: collectionSummaryType == "hiddenItems"
? t("download_hidden_items")
: t("download_album")
}
>
<IconButton onClick={onClick}>
<FileDownloadOutlinedIcon />
</IconButton>
</Tooltip>
);
const showShareQuickOption = (type: CollectionSummaryType) =>
type == "folder" ||
type == "album" ||
type == "outgoingShare" ||
type == "sharedOnlyViaLink" ||
type == "archived" ||
type == "incomingShareViewer" ||
type == "incomingShareCollaborator" ||
type == "pinned";
interface ShareQuickOptionProps {
onClick: () => void;
collectionSummaryType: CollectionSummaryType;
}
const ShareQuickOption: React.FC<ShareQuickOptionProps> = ({
onClick,
collectionSummaryType,
}) => (
<Tooltip
title={
collectionSummaryType == "incomingShareViewer" ||
collectionSummaryType == "incomingShareCollaborator"
? t("sharing_details")
: collectionSummaryType == "outgoingShare" ||
collectionSummaryType == "sharedOnlyViaLink"
? t("modify_sharing")
: t("share_album")
}
>
<IconButton onClick={onClick}>
<PeopleIcon />
</IconButton>
</Tooltip>
);
const EmptyTrashOption: React.FC<OptionProps> = ({ onClick }) => (
<OverflowMenuOption
color="critical"
startIcon={<DeleteOutlinedIcon />}
onClick={onClick}
>
{t("empty_trash")}
</OverflowMenuOption>
);
type DownloadOptionProps = OptionProps & {
isDownloadInProgress?: () => boolean;
};
const DownloadOption: React.FC<
React.PropsWithChildren<DownloadOptionProps>
> = ({ isDownloadInProgress, onClick, children }) => (
<OverflowMenuOption
startIcon={
isDownloadInProgress && isDownloadInProgress() ? (
<EnteSpinner size="20px" sx={{ cursor: "not-allowed" }} />
) : (
<FileDownloadOutlinedIcon />
)
}
onClick={onClick}
>
{children}
</OverflowMenuOption>
);
interface SharedCollectionOptionProps {
isArchived: boolean;
onArchiveClick: () => void;
onUnarchiveClick: () => void;
onLeaveSharedAlbumClick: () => void;
onCastClick: () => void;
}
const SharedCollectionOptions: React.FC<SharedCollectionOptionProps> = ({
isArchived,
onArchiveClick,
onUnarchiveClick,
onLeaveSharedAlbumClick,
onCastClick,
}) => (
<>
{isArchived ? (
<OverflowMenuOption
onClick={onUnarchiveClick}
startIcon={<Unarchive />}
>
{t("unarchive_album")}
</OverflowMenuOption>
) : (
<OverflowMenuOption
onClick={onArchiveClick}
startIcon={<ArchiveOutlined />}
>
{t("archive_album")}
</OverflowMenuOption>
)}
<OverflowMenuOption
startIcon={<LogoutIcon />}
onClick={onLeaveSharedAlbumClick}
>
{t("leave_album")}
</OverflowMenuOption>
<OverflowMenuOption startIcon={<TvIcon />} onClick={onCastClick}>
{t("cast_album_to_tv")}
</OverflowMenuOption>
</>
);
interface AlbumCollectionOptionsProps {
isArchived: boolean;
isPinned: boolean;
isHidden: boolean;
onRenameClick: () => void;
onSortClick: () => void;
onArchiveClick: () => void;
onUnarchiveClick: () => void;
onPinClick: () => void;
onUnpinClick: () => void;
onHideClick: () => void;
onUnhideClick: () => void;
onDeleteClick: () => void;
onShareClick: () => void;
onCastClick: () => void;
}
const AlbumCollectionOptions: React.FC<AlbumCollectionOptionsProps> = ({
isArchived,
isPinned,
isHidden,
onRenameClick,
onSortClick,
onArchiveClick,
onUnarchiveClick,
onPinClick,
onUnpinClick,
onHideClick,
onUnhideClick,
onDeleteClick,
onShareClick,
onCastClick,
}) => (
<>
<OverflowMenuOption onClick={onRenameClick} startIcon={<EditIcon />}>
{t("rename_album")}
</OverflowMenuOption>
<OverflowMenuOption onClick={onSortClick} startIcon={<SortIcon />}>
{t("sort_by")}
</OverflowMenuOption>
{isPinned ? (
<OverflowMenuOption
onClick={onUnpinClick}
startIcon={<UnPinIcon />}
>
{t("unpin_album")}
</OverflowMenuOption>
) : (
<OverflowMenuOption
onClick={onPinClick}
startIcon={<PushPinOutlined />}
>
{t("pin_album")}
</OverflowMenuOption>
)}
{!isHidden && (
<>
{isArchived ? (
<OverflowMenuOption
onClick={onUnarchiveClick}
startIcon={<Unarchive />}
>
{t("unarchive_album")}
</OverflowMenuOption>
) : (
<OverflowMenuOption
onClick={onArchiveClick}
startIcon={<ArchiveOutlined />}
>
{t("archive_album")}
</OverflowMenuOption>
)}
</>
)}
{isHidden ? (
<OverflowMenuOption
onClick={onUnhideClick}
startIcon={<VisibilityOutlined />}
>
{t("unhide_collection")}
</OverflowMenuOption>
) : (
<OverflowMenuOption
onClick={onHideClick}
startIcon={<VisibilityOffOutlined />}
>
{t("hide_collection")}
</OverflowMenuOption>
)}
<OverflowMenuOption
startIcon={<DeleteOutlinedIcon />}
onClick={onDeleteClick}
>
{t("delete_album")}
</OverflowMenuOption>
<OverflowMenuOption onClick={onShareClick} startIcon={<PeopleIcon />}>
{t("share_album")}
</OverflowMenuOption>
<OverflowMenuOption startIcon={<TvIcon />} onClick={onCastClick}>
{t("cast_album_to_tv")}
</OverflowMenuOption>
</>
);
interface CollectionSortOrderMenuProps {
open: boolean;
onClose: () => void;
overFlowMenuIconRef: React.MutableRefObject<SVGSVGElement>;
onAscClick: () => void;
onDescClick: () => void;
}
const CollectionSortOrderMenu: React.FC<CollectionSortOrderMenuProps> = ({
open,
onClose,
overFlowMenuIconRef,
onAscClick,
onDescClick,
}) => {
const handleAscClick = () => {
onClose();
onAscClick();
};
const handleDescClick = () => {
onClose();
onDescClick();
};
return (
<StyledMenu
id={"collection-files-sort"}
anchorEl={overFlowMenuIconRef.current}
open={open}
onClose={onClose}
MenuListProps={{
disablePadding: true,
"aria-labelledby": "collection-files-sort",
}}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
}}
transformOrigin={{
vertical: "top",
horizontal: "right",
}}
>
<OverflowMenuOption onClick={handleDescClick}>
{t("newest_first")}
</OverflowMenuOption>
<OverflowMenuOption onClick={handleAscClick}>
{t("oldest_first")}
</OverflowMenuOption>
</StyledMenu>
);
};

View File

@@ -1,36 +0,0 @@
import { FlexWrapper } from "@ente/shared/components/Container";
import { Box, Typography } from "@mui/material";
import { t } from "i18next";
import React from "react";
interface Iprops {
name: string;
fileCount: number;
endIcon?: React.ReactNode;
}
export function CollectionInfo({ name, fileCount, endIcon }: Iprops) {
return (
<div>
<Typography variant="h3">{name}</Typography>
<FlexWrapper>
<Typography variant="small" color="text.muted">
{t("photos_count", { count: fileCount })}
</Typography>
{endIcon && (
<Box
sx={{
svg: {
fontSize: "17px",
color: "text.muted",
},
}}
ml={1.5}
>
{endIcon}
</Box>
)}
</FlexWrapper>
</div>
);
}

View File

@@ -1,72 +0,0 @@
import type { Collection } from "@/media/collection";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import ArchiveOutlined from "@mui/icons-material/ArchiveOutlined";
import Favorite from "@mui/icons-material/FavoriteRounded";
import LinkIcon from "@mui/icons-material/Link";
import PeopleIcon from "@mui/icons-material/People";
import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer";
import CollectionOptions from "components/Collections/CollectionOptions";
import type { Dispatch, SetStateAction } from "react";
import { CollectionSummary, CollectionSummaryType } from "types/collection";
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import { shouldShowOptions } from "utils/collection";
import { CollectionInfo } from "./CollectionInfo";
import { CollectionInfoBarWrapper } from "./styledComponents";
interface Iprops {
activeCollection: Collection;
collectionSummary: CollectionSummary;
setCollectionNamerAttributes: SetCollectionNamerAttributes;
showCollectionShareModal: () => void;
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
isActiveCollectionDownloadInProgress: () => boolean;
setActiveCollectionID: (collectionID: number) => void;
setShowAlbumCastDialog: Dispatch<SetStateAction<boolean>>;
}
export default function CollectionInfoWithOptions({
collectionSummary,
...props
}: Iprops) {
if (!collectionSummary) {
return <></>;
}
const { name, type, fileCount } = collectionSummary;
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
switch (type) {
case CollectionSummaryType.favorites:
return <Favorite />;
case CollectionSummaryType.archived:
return <ArchiveOutlined />;
case CollectionSummaryType.incomingShareViewer:
case CollectionSummaryType.incomingShareCollaborator:
return <PeopleIcon />;
case CollectionSummaryType.outgoingShare:
return <PeopleIcon />;
case CollectionSummaryType.sharedOnlyViaLink:
return <LinkIcon />;
default:
return <></>;
}
};
return (
<CollectionInfoBarWrapper>
<SpaceBetweenFlex>
<CollectionInfo
name={name}
fileCount={fileCount}
endIcon={<EndIcon type={type} />}
/>
{shouldShowOptions(type) && (
<CollectionOptions
{...props}
collectionSummaryType={type}
/>
)}
</SpaceBetweenFlex>
</CollectionInfoBarWrapper>
);
}

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