Compare commits

..

138 Commits

Author SHA1 Message Date
Prateek Sunal
f0982e01d7 fix: add temporary changes 2025-02-17 18:52:17 +05:30
github-actions[bot]
dd52ee7763 [auth] New translations (#5085)
New translations from
[Crowdin](https://crowdin.com/project/ente-authenticator-app)

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-02-17 11:02:52 +05:30
Manav Rathi
387e4ae826 [server] Fix publish workflow (#5089)
Fixes for https://github.com/ente-io/ente/pull/5088
2025-02-17 11:01:34 +05:30
Andrés Ignacio Torres
098ff4e664 [auth] Add custom icon for EVE Online (#5078)
## Description

Added an Ente Auth custom icon for the EVE Online game service. I
followed the guidance
[here](https://github.com/ente-io/ente/blob/main/auth/docs/adding-icons.md)
to add the entry for EVE Online.

The icon itself comes from [Wikimedia
Commons](https://commons.wikimedia.org/wiki/File:EVE_online_logo.svg)
and was adapted to a square. I also added a custom hex color code to
ensure the logo is visible when using dark mode.

Noting that this is my first contribution as I've recently started using
Ente Auth, any feedback or suggestions would be appreciated :-)

## Tests

Visual change, no code changes.
2025-02-17 11:01:21 +05:30
Manav Rathi
00a04f18e4 [server] Fix publish workflow
Fixes for https://github.com/ente-io/ente/pull/5088
2025-02-17 11:00:21 +05:30
Rflew33
b8304f0ec5 Additional Icons (#5072)
## Description
Added Aruba. OnShape, RealVNC icons
## Tests
2025-02-17 10:59:39 +05:30
Manav Rathi
979fa5e6da [server] Publish Docker image automatically on 15th of every month (#5088) 2025-02-17 10:58:54 +05:30
Manav Rathi
91f356ceda [server] Publish Docker image automatically on 15th of every month 2025-02-17 10:51:23 +05:30
Manav Rathi
a019aaf5fc [web] New translations (#5084)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2025-02-17 10:18:51 +05:30
Crowdin Bot
a9df48ea5d New Crowdin translations by GitHub Action 2025-02-17 00:36:04 +00:00
Vishnu Mohandas
be6ce6d639 feat: create video-streaming.md (#5080)
## Description

Add FAQs for streaming

## Tests
2025-02-16 21:31:36 +05:30
vishnukvmd
9d421e79a7 Cosmetic changes 2025-02-16 21:30:25 +05:30
Prateek Sunal
d1d8144fd1 Update sidebar.ts 2025-02-16 19:59:12 +05:30
Prateek Sunal
7302f1d4ab feat: create video-streaming.md 2025-02-16 19:54:53 +05:30
Ashil
39788341db [docs] Upgrade background sync doc: Mention how background sync won't work if app is in private space on android 15 and later (#5067) 2025-02-14 19:41:45 +05:30
Neeraj
6c86fe0d53 [mob] Remove internal flag (#5071)
## Description

## Tests
2025-02-14 19:34:09 +05:30
Neeraj Gupta
30ed06cfda [mob] Remove internal flag 2025-02-14 19:30:41 +05:30
Prateek Sunal
21788c28cf [mob] streaming fixes (#5055)
## Description

## Tests
2025-02-14 18:28:25 +05:30
Manav Rathi
2969b5c9a5 [web] Code restructuring - Part 2 (#5070)
Continuation of https://github.com/ente-io/ente/pull/5069
2025-02-14 18:22:56 +05:30
Manav Rathi
aa74948f4a Workaround bad types is file 2025-02-14 18:14:04 +05:30
Manav Rathi
1bdbfe0580 More 2025-02-14 18:06:13 +05:30
Manav Rathi
cacf4212c7 Silence some 2025-02-14 18:05:16 +05:30
Manav Rathi
8f540f23dc Types 2025-02-14 18:03:02 +05:30
Manav Rathi
17d76e50da Fix lints 2025-02-14 18:00:33 +05:30
Manav Rathi
db26923d68 Move 2025-02-14 17:56:38 +05:30
Manav Rathi
4670be9bba Move 2025-02-14 17:52:36 +05:30
Manav Rathi
f8c2f4b9dd Move 2025-02-14 17:38:28 +05:30
Manav Rathi
396065e80c Move 2025-02-14 17:30:03 +05:30
Manav Rathi
5a755d851a Move 2025-02-14 17:12:17 +05:30
Manav Rathi
286a968f65 Props 2025-02-14 17:03:39 +05:30
Manav Rathi
efff97bc71 [web] Code restructuring (#5069) 2025-02-14 16:35:31 +05:30
Manav Rathi
2a73de848c Lint 2025-02-14 16:25:41 +05:30
Neeraj
c7c8fd65b6 [mob] Fix collection attribute motification (#5068)
## Description

## Tests
2025-02-14 15:56:17 +05:30
Neeraj Gupta
8d7eef99ad [mob] Fix collection attribute motification 2025-02-14 15:54:14 +05:30
Manav Rathi
1605b44c6e Fin 2025-02-14 15:30:35 +05:30
ashilkn
578a92d4bc [docs] Upgrade background sync doc: Mention how background sync won't work if app is in private space on android 15 and later 2025-02-14 15:18:58 +05:30
Manav Rathi
bf3ed6f478 Namespace 2025-02-14 15:06:39 +05:30
Manav Rathi
92a9698df5 Prune 2025-02-14 14:59:03 +05:30
Manav Rathi
342ac3258a Direct 2025-02-14 14:56:54 +05:30
Manav Rathi
e4427d7605 Direct 2025-02-14 14:52:11 +05:30
Manav Rathi
6f729c01e1 Direct 2025-02-14 14:47:05 +05:30
Manav Rathi
0d7c319903 Swap 2025-02-14 14:41:10 +05:30
Manav Rathi
6d552f5190 Unnest 2025-02-14 14:29:43 +05:30
Manav Rathi
06450a0ce0 Tweak 2025-02-14 14:23:56 +05:30
Manav Rathi
72d6789739 In photos 2025-02-14 14:22:53 +05:30
Manav Rathi
3d2d0cc345 More 2025-02-14 14:20:08 +05:30
Manav Rathi
884246d2ab Provide 2025-02-14 14:17:29 +05:30
Manav Rathi
cf25cc40e4 Extra 2025-02-14 14:09:42 +05:30
Manav Rathi
7138510e48 Rename 2025-02-14 14:01:56 +05:30
Manav Rathi
15e7e0ae9d Move 2025-02-14 13:58:53 +05:30
Manav Rathi
9dcced260f Same nomenclature 2025-02-14 13:21:12 +05:30
Manav Rathi
2d5dc734aa Up 2025-02-14 13:17:44 +05:30
Manav Rathi
1d93d44180 ud 2025-02-14 13:10:43 +05:30
Manav Rathi
0aeb9f0c82 Up 2025-02-14 12:59:43 +05:30
Manav Rathi
183bbdd145 Fix 2025-02-14 12:26:30 +05:30
Manav Rathi
8d701d4fd5 Match reality 2025-02-14 12:13:45 +05:30
Manav Rathi
c6f6041d24 Pass context method 2025-02-14 12:07:31 +05:30
Manav Rathi
f49ece10e6 Move 2025-02-14 11:57:45 +05:30
Manav Rathi
d0f206741f Undep 2025-02-14 11:51:25 +05:30
Manav Rathi
87ff5c5c0b Undep 2025-02-14 11:37:41 +05:30
Manav Rathi
b931dac18b Doc 2025-02-14 11:30:32 +05:30
Manav Rathi
2b52616ba5 Undep 2025-02-14 11:11:57 +05:30
Manav Rathi
e66ee5bcb1 to-base 2025-02-14 11:08:56 +05:30
Manav Rathi
f18bcc71d3 Move 2025-02-14 11:03:16 +05:30
Manav Rathi
43a7cb1223 Move 2025-02-14 10:48:38 +05:30
Manav Rathi
ceb25651f2 [web] Use upstream PhotoSwipe (Much WIP) (#5066) 2025-02-14 08:54:25 +05:30
Manav Rathi
8a8934eacd LF 2025-02-14 08:48:54 +05:30
Manav Rathi
20fea517ce Revert "Workbench"
This reverts commit 4736ec7e0a.
2025-02-14 08:46:13 +05:30
Manav Rathi
0d32bd55dd Plumb 2025-02-14 08:34:11 +05:30
Manav Rathi
20bbdb131d Get file 2025-02-14 08:28:11 +05:30
Manav Rathi
1980cb035e Add example 2025-02-14 08:28:11 +05:30
Manav Rathi
bd00c27dc6 Same name as docs 2025-02-14 08:28:11 +05:30
Manav Rathi
e8fa86e2ad Loader 2025-02-14 08:28:11 +05:30
Manav Rathi
baa72202b2 Icon 2 2025-02-14 08:28:11 +05:30
Manav Rathi
46658a26f3 Icon 1 2025-02-14 08:28:11 +05:30
Manav Rathi
6653b36764 Use order to position it 2025-02-14 08:28:11 +05:30
Manav Rathi
c17d0d0087 Handle keyboard focus when auto hiding 2025-02-14 08:28:11 +05:30
Manav Rathi
b823a8d6a1 Cleanup 2025-02-14 08:28:11 +05:30
Manav Rathi
e06b20a566 Fix 2025-02-14 08:28:11 +05:30
Manav Rathi
8218bfba04 Try and alternative event to resize 2025-02-14 08:28:11 +05:30
Manav Rathi
8df5831944 Some overlay 2025-02-14 08:28:11 +05:30
Manav Rathi
6e774d6758 Pause on changing video slide 2025-02-14 08:28:11 +05:30
Manav Rathi
981c74d3f1 Don't auto load vids
Ref: 5e32d6589d/dist/photoswipe-video-plugin.esm.js (L77)
2025-02-14 08:28:11 +05:30
Manav Rathi
18ee3b19f7 Set 2025-02-14 08:28:11 +05:30
Manav Rathi
aa27191ddc CSS for vids 2025-02-14 08:28:11 +05:30
Manav Rathi
0883fe1d05 Take 2 disable thumb zoom 2025-02-14 08:28:11 +05:30
Manav Rathi
17e59de59c Revert "Note diversion that didn't work"
This reverts commit 0791a8f659e7aad48ff69c53f07c7894db663345.
2025-02-14 08:28:11 +05:30
Manav Rathi
bdb30d64f0 Note diversion that didn't work 2025-02-14 08:28:11 +05:30
Manav Rathi
57881f34c3 Image dims 2025-02-14 08:28:11 +05:30
Manav Rathi
6ef3c01030 Fix 2025-02-14 08:28:11 +05:30
Manav Rathi
d4ddc0f919 The dimensions are necessary
the CSS was masking the issue
2025-02-14 08:28:11 +05:30
Manav Rathi
4736ec7e0a Workbench 2025-02-14 08:28:11 +05:30
Neeraj
0840c66a34 [mob] Mark collection owner, sharee and publicUrls as non-nullable field (#5063)
## Description

## Tests
2025-02-13 16:15:37 +05:30
Neeraj Gupta
eb2f6aec68 [mob] refactor 2025-02-13 16:12:56 +05:30
mangesh
45074f85d9 [server] API for modifying family member storage limit (#5028)
## Tests 

Tested in followin cases 
1. various statuses "REJECTED", "REVOKED" and "SELF" etc
2. user not part of any family & admin user
3. storage limit check on bulk upload
4. reduce storage check 
5. set storage bigger than anything available in the world
6. admin shouldn't be able to set his own storage.

- [x] check for potential refactoring
2025-02-13 16:07:16 +05:30
mngshm
c46c27d21d if storageLimit == null, set no limit for member 2025-02-13 16:03:20 +05:30
Neeraj Gupta
3ff8d04d7b [mob] Mark collection owner, sharee and publicUrls as non-nullable field 2025-02-13 15:36:20 +05:30
Prateek Sunal
437eb246b0 fix: reduce it to 10 minutes minimum 2025-02-13 15:35:32 +05:30
Laurens Priem
5e383f3844 [mob][photos] bump for internal release (#5061)
## Description

## Tests
2025-02-13 15:25:07 +05:30
Manav Rathi
9bce8dc878 [desktop] Dark WCO title bar overlay (#5062) 2025-02-13 15:24:13 +05:30
Manav Rathi
a447d615e0 [desktop] Dark WCO title bar overlay 2025-02-13 15:22:54 +05:30
laurenspriem
239e6a3158 [mob][photos] bump for internal release 2025-02-13 15:21:35 +05:30
mngshm
5a72d62555 chore: validate memberUsage check inside *storageLimit nil check 2025-02-13 15:08:18 +05:30
mngshm
7aa8f6f00f chore: wrap everything inside nil check for storageLimit 2025-02-13 14:59:52 +05:30
Manav Rathi
5b168021f4 [desktop] Add IndexedDB circuit breaker (#5058)
From one customer's logs (Windows):

[rndr] [error] Unhandled promise rejection: Error: The user's token was
present in local storage but not in IndexedDB

And thereafter the app started behaving erratically. Restarting fixed
it. This
sequence happened during an app update.

This sequence is not reproducible, but adding as a extra precaution
adding a
circuit breaker to prevent execution if IndexedDB is not readable.
2025-02-13 14:48:14 +05:30
mngshm
a407b1baad fix: allow modification for invited user
fix: adds bonus + storage to get total storage

fix: var name changes, use NewBadRequestWithmessage
2025-02-13 14:36:46 +05:30
Manav Rathi
3589cc5bbf Lint 2025-02-13 14:35:08 +05:30
Manav Rathi
0cef0656f3 [desktop] Add IndexedDB circuit breaker
From one customer's logs (Windows):

[rndr] [error] Unhandled promise rejection: Error: The user's token was present in local storage but not in IndexedDB

And thereafter the app started behaving erratically. Restarting fixed it. This
sequence happened during an app update.

This sequence is not reproducible, but adding as a extra precaution adding a
circuit breaker to prevent execution if IndexedDB is not readable.
2025-02-13 14:29:34 +05:30
mngshm
3b3ba721a2 revert errors.go 2025-02-13 14:17:19 +05:30
Manav Rathi
d899be6eac [web] Improve logging of new HTTP errors (#5057)
...to match how the old HTTPService ones would've behaved.
2025-02-13 13:16:13 +05:30
Manav Rathi
17c713d3de Ignore the temporary desktop build folder 2025-02-13 13:11:09 +05:30
Manav Rathi
0e9153f4ab [web] Improve logging of new HTTP errors
...to match how the old HTTPService ones would've behaved.
2025-02-13 13:06:26 +05:30
Laurens Priem
5484a95bf4 Ram logging (#5056)
## Description

Extra debug options related to ML
2025-02-13 12:55:34 +05:30
Laurens Priem
2a1c1a30e9 [mob][photos] Fix empty facethumbnail due to decoding issue (#5054)
## Description

Resolve face thumbnail issue.
2025-02-13 12:52:37 +05:30
laurenspriem
f902b7e75c [mob][photos] ML dev options 2025-02-13 12:51:38 +05:30
Prateek Sunal
ac9f4e3181 fix: behavior 2025-02-13 12:46:43 +05:30
Prateek Sunal
b68b1a97b5 Merge branch 'main' into streaming-release-ready 2025-02-13 12:45:08 +05:30
Prateek Sunal
b8de2bf736 chore: update locks 2025-02-13 12:41:06 +05:30
Prateek Sunal
d35975b26e fix(preview-queue): put higher duration videos at last 2025-02-13 12:13:52 +05:30
laurenspriem
c2ca87d3af [mob][photos] log device ram 2025-02-13 12:13:14 +05:30
mngshm
a41c359ae4 use FetchMembersForAdminID to get storage including bonuses
remove unneeded api calls

omit unnecessary checks
2025-02-13 11:57:36 +05:30
Neeraj
e00cdee92b [auth] Add icon for Ankama & Seafile (#5030)
## Description

SVG Icon added for [Ankama](https://www.ankama.com/) and
[Seafile](https://www.seafile.com/en/home/)

Source of the icon is their website then cropped
2025-02-13 11:18:51 +05:30
laurenspriem
b1ce7b6edb [mob][photos] Fix empty facethumbnail due to decoding issue 2025-02-13 11:16:32 +05:30
mngshm
15d58e3446 make linters happy 2025-02-12 22:23:04 +05:30
Prateek Sunal
677a473d7d fix: use crf 23 for all the compressions for better results 2025-02-12 20:39:54 +05:30
Prateek Sunal
9e12f35650 fix: check before file size and chunk size 2025-02-12 14:49:23 +05:30
mngshm
a7f31119fe [server]fix: use proper Error causes 2025-02-12 14:47:09 +05:30
mngshm
b729b8f0ea [server]refactor: make code more readable with guard clause 2025-02-12 11:08:19 +05:30
Yannick
b933a89336 [auth] Add icon for Seafile 2025-02-11 18:32:40 +01:00
Yannick
016a476895 [auth] Add icon for Ankama 2025-02-11 16:58:19 +01:00
mngshm
4ee6ef408e [server] error handling if memberUsage is more than potential modified storagelimit
some more additional checks for the adminUser
2025-02-11 19:07:19 +05:30
mngshm
79712182af [server] include usageRepo inside family controller 2025-02-11 18:34:41 +05:30
mngshm
38a35696a3 fix column names in DB & include UsageCtrl in controllers 2025-02-11 17:28:30 +05:30
mngshm
bf4807da5b [server] use custom request struct for modifying functionality 2025-02-11 17:20:04 +05:30
Prateek Sunal
dc3f074588 fix: don't index unowned files 2025-02-11 17:14:20 +05:30
mngshm
8da160b834 minor fix for db column names in DB.Exec 2025-02-11 11:51:47 +05:30
mngshm
2947ca2e3c fix storagelimit column name in DB.Exec 2025-02-11 11:27:21 +05:30
mngshm
59e26779b9 [server][WIP] functionality for modifying users storage limit 2025-02-10 19:36:40 +05:30
180 changed files with 1769 additions and 1248 deletions

View File

@@ -1,27 +1,24 @@
name: "Publish ghcr (server)"
on:
# Run manually, providing it the commit.
#
# To obtain the commit from the currently deployed museum, do:
# curl -s https://api.ente.io/ping | jq -r '.id'
#
# See server/docs/publish.md for more details.
# Run automatically on 15th of every month, at 05:00 UTC.
schedule:
- cron: '0 5 15 * *'
# Run manually if needed to publish out of schedule.
workflow_dispatch:
inputs:
commit:
description: "Commit to publish the image from"
type: string
required: true
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Determine commit from prod museum
run: |
echo "museum_commit=$(curl -s https://api.ente.io/ping | jq -r .id)" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit }}
ref: ${{ env.museum_commit }}
- name: Build and push
uses: mr-smithers-excellent/docker-build-push@v6
@@ -34,8 +31,8 @@ jobs:
enableBuildKit: true
multiPlatform: true
platform: linux/amd64,linux/arm64
buildArgs: GIT_COMMIT=${{ inputs.commit }}
tags: ${{ inputs.commit }}, latest
buildArgs: GIT_COMMIT=${{ env.museum_commit }}
tags: ${{ env.museum_commit }}, latest
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -35,9 +35,18 @@
{
"title": "Amazon"
},
{
"title": "Ankama",
"slug": "ankama"
},
{
"title": "Anycoin Direct",
"slug": "anycoindirect"
},
{
"title": "Aruba",
"slug": "aruba",
"hex": "ef8a33"
},
{
"title": "AscendEX"
@@ -352,6 +361,14 @@
{
"title": "Estateguru"
},
{
"title": "EVEOnline",
"slug": "eve_online",
"altNames": [
"EVE Online"
],
"hex": "858585"
},
{
"title": "Fastmail"
},
@@ -760,6 +777,11 @@
"altNames": [
"欧易"
]
},
{
"title": "OnShape",
"slug": "onshape",
"hex": "7abb5e"
},
{
"title": "Parqet",
@@ -857,6 +879,11 @@
{
"title": "RealMe",
"slug": "realme"
},
{
"title": "RealVNC",
"slug": "realvnc",
"hex": "488aec"
},
{
"title": "Registro br",
@@ -901,6 +928,10 @@
{
"title": "Samsung"
},
{
"title": "Seafile",
"slug": "seafile"
},
{
"title": "Sendgrid"
},

View File

@@ -0,0 +1,5 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720" width="720" height="720">
<title>ankama</title>
<path class="s0" d="m572.3 253.3c-0.3-1.4-0.5-3-1.1-4.4-15.1-46-59.7-76.2-107.9-71.5-31.2 3-55.6 18.9-73.4 44.7-3.8 5.7-7.1 12-10.1 18.4-9.4 19.5-19.5 38.7-24.7 59.7-0.5 1.6-1.1 3.5-1.9 4.9-2.5 4.6-5.5 6.3-10.1 4.1-11.7-5.7-23.8-5.2-36.1-4.4-20.3 1.1-37.9-5.7-52.9-19.5-36.1-33.1-35.3-91 1.9-123 26.8-23.3 43.3-52.1 49.3-86.8 3-16.5 3.3-33.4 0-49.9-0.5-2.7-0.8-5.5-1.4-8.7 7.4-1.4 14.3 0 20.8 2.2 42.8 12.4 69.3 40.6 81.6 82.7 0.5 2.2 1.1 4.4 1.6 6.8 0.8 3 2.7 4.9 5.7 5.5 3.3 0.5 6.3-0.5 7.9-3.5 3-6.3 5.7-12.7 8.2-19.2 3.3-9.7 3.8-10.5 14.3-10.6 58.3-0.8 111.7 15.4 159.4 49 60.5 42.9 98.4 101.4 115.7 173.4 0.8 3.5 0.8 7.6 0.5 11.6-4.6 68-26.8 129.6-68.8 183.5-49 63.1-112.4 104.4-190.7 121.4-72.6 15.8-141.7 4.9-207.1-30.1-1.4-0.8-2.7-1.6-3.8-2.5-0.3 0-0.5-0.8-1.6-2.2 8.7 3 17 5.5 24.7 8.6 13.9 5.7 28.5 8.7 43.6 8.6 14.3 0 28.2 0.8 42.5 0 63.9-3.8 119.5-27.4 166.2-70.7 23.6-21.9 32.8-50.7 30.7-82.4-1.9-32.3-16.5-58.6-42.2-78.3-18.9-14.6-32-33.1-38-56.4-4.1-15.8-4.6-32-0.8-48.2 5.7-24.7 27.7-41.7 52.3-40.6 17 0.8 29.6 9 39.5 22.5 1.4 1.9 2.7 4.1 4.1 6.3 0.5 0 0.8-0.5 1.4-0.8l0.8 0.3v-0.5zm-263.5-55.1c-14.7 0-26.3 12.4-26.3 27.7 0 15.3 11.6 27.4 26.6 27.4 15 0 26-12.4 26-27.1 0-14.7-12-27.9-26.3-27.9v-0.2z"/>
<path class="s0" d="m168.2 314.5c7.4 1.6 14.7 3.3 22.5 4.9 10.9 2.2 14.3 6.8 12 18.1-1.6 8.2-3 16.2-4.9 24.1-0.8 3.5 0 5.7 2.5 8.2 22.8 23.6 50.1 38.4 83.8 43.6-2.2-1.4-3-2.2-4.1-2.7-19.5-10.1-27.7-25.2-25.2-46.6 1.1-9.8 0-19.2-6-27.4-3.8-5.5-9-9.7-13.9-14.3-1.6-1.6-3.8-2.5-6-4.1 5.2-3.3 10.5-3.3 15.4-3 7.4 0.3 14.7 1.4 22.2 3 11.6 2.5 21.7 8.2 30.4 16.2 6.5 6 12.4 12.4 18.7 18.4 13.9 13.6 25.2 12.8 37.2-2.7 7.6-9.8 12.4-21.4 15.8-33.1 3.8-12.7 8.2-24.9 15.4-36.1 7.4-11.6 16.5-21.7 27.4-30.1 9.7-7.4 19.7-6.8 30.7-3.3v9.7c-1.1 27.1 5.2 52.6 19.5 75.9 1.1 1.9 2.2 4.1 3.5 6 4.1 6.3 3.3 12-0.5 18.1-5.5 8.7-13.5 13.2-23.8 13.5h-6.3c-12.7 0-23.3 8.2-26.8 20.6-3 10.9 1.9 23.3 11.7 29.6 10.9 6.8 24.1 5.7 33.4-3 10.1-9.8 16.5-21.7 18.9-35.7 0.3-1.9 0.8-4.1 1.1-6 17.3-0.3 39.8 16.6 48.2 37.2 10.1 24.1 6.3 46.6-10.1 68.8-0.8-5.2-1.4-9.4-2.2-12.8-2.2-9.7-8.6-15.7-18.1-17.7-6.8-1.6-9.8 0-13.6 5.7-1.9 3-3.5 6.3-5.2 9.7-2.5 4.9-4.6 9.8-7.1 14.6-13.6 27.1-34.9 45.8-63.2 55.6-27.1 9.7-55.2 13.2-83.5 10.1-16.5-1.6-29.3-10.5-39.8-23.6 1.9-1.4 3.3-2.7 4.9-3.8 7.4-5.5 12.8-12.7 16.6-20.8 1.1-2.2 1.6-4.9 1.9-7.4 0.5-5.2-2.7-9.4-7.6-9.8-4.6-0.5-8.7 2.5-10.1 7.6-0.8 3.3-1.1 6.5-2.5 9.7-3.8 7.1-9.7 12.8-16.5 17-7.4 4.4-14.6 3.8-20-1.4-5.5-5.5-6-11.7-1.9-19.7 0.3-0.5 0.5-1.4 1.4-3-2.5 1.1-4.1 1.4-5.5 2.2-17.7 10.6-25.8 31.5-19.2 51.5 18.7 56.7-6 119.5-54 151-3.3 2.2-7.1 4.1-11.6 6.8 0-3-0.5-5.2-0.5-7.4-2.5-57.8-30.1-98.9-82.4-122.8-32.8-14.7-53.7-39.5-62.1-74-12.4-50.1 13.9-102.2 61.2-122.8 1.9-0.8 4.1-1.4 7.6-2.7-15.1 28.8-18.7 57.2-9.4 86.8 5.5 17.3 14.7 32 29 45.5 0.3-12.4-0.8-23.6 7.6-33.4 2.7 6.3 5.5 12 8.2 17.7 4.9 10.9 12.7 19.5 23.3 24.9 12 6.3 24.1 6.8 36.5 1.1 8.6-3.8 15.8-9.4 22.2-16.2 8.6-9 8.6-24.9 0.3-35.7-5.5-7.1-8.6-14.7-7.4-23.6 1.9-15.4 15.1-26.8 32-28.2 4.6-0.3 9.4 0 13.6 0.5 4.1 0.8 8.2 2.5 12.7 3.3-3.5-3.8-7.9-6.5-12.7-8.2-8.2-2.7-16.6-3.3-25.2-1.6-14.7 3.3-24.9 16.2-26.3 33.4-0.5 6.3 0 12.8 1.1 19.2 3 16.5-0.8 30.4-13.2 42.8-1.9-1.6-3.8-2.7-5.7-4.4-43.9-37.6-69.6-84.6-75.6-142.2-10.1-95.9 42.5-184.9 130.9-223 28.5-12.4 46-33.8 53.4-63.9 0.3-1.4 0.8-2.7 1.9-3.8 1.9 10.1 1.4 20.3-1.1 30.1-6 24.1-19.5 42.8-41.4 55.3-21.4 12.4-38.4 29-51.5 50.1-2.5 4.1-2.7 7.1 0 10.9 2.7 3.5 5.2 7.6 7.6 11.6 4.4 7.4 3 13.9-4.1 18.9-6.3 4.4-13.2 8.2-19.5 12.7-1.6 1.1-3.5 3.3-3.8 5.2-1.6 18.7-0.5 36.9 5.5 55.3l1.4-0.3-0.2-0.2zm192.8 132c14.3 0 25.8-11.3 25.5-25.2 0-13.6-12-25.8-25.5-25.8-13.5 0-25.2 11.3-25.5 25.5 0 14.3 11.3 25.5 25.2 25.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#FF8300" fill-rule="evenodd" d="M12.1099561,17.3015551 C9.03598293,17.3015551 6.50849391,14.8423766 6.50849391,11.836714 C6.50849391,8.83105139 9.03598293,6.37187289 12.1099561,6.37187289 C15.1839292,6.37187289 17.7114182,8.83105139 17.7114182,11.836714 C17.7114182,14.8423766 15.1839292,17.3015551 12.1099561,17.3015551 L12.1099561,17.3015551 Z M12.1099561,2 C6.50849391,2 2,6.4401834 2,11.836714 C2,17.3015551 6.50849391,21.673428 12.1099561,21.673428 C14.4325135,21.673428 16.5501395,20.9220123 18.2579023,19.6241126 C19.28256,21.3318754 22.2199121,21.673428 22.2199121,21.673428 L22.2199121,11.836714 C22.2199121,6.4401834 17.7114182,2 12.1099561,2 L12.1099561,2 Z"/>
</svg>

After

Width:  |  Height:  |  Size: 901 B

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" 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="198.4" height="198.4" xml:space="preserve">
<path transform="translate(0, 60)" d="M 0,0 0,13.88 10.97,13.88 10.97,10.31 60.69,10.31 60.69,0 0,0 z M 65.84,0 99.22,58.09 132.6,0 120.7,0 C 120.7,0 100.5,34.91 99.22,37.16 97.92,34.91 77.75,0 77.75,0 L 65.84,0 z M 137.8,0 137.8,13.88 148.7,13.88 148.7,10.31 198.4,10.31 198.4,0 137.8,0 z M 0,19.12 0,29.47 60.69,29.47 60.69,19.12 0,19.12 z M 137.8,19.12 137.8,29.47 198.4,29.47 198.4,19.12 137.8,19.12 z M 0,34.66 0,48.59 60.69,48.59 60.69,38.25 10.97,38.25 10.97,34.66 0,34.66 z M 137.8,34.66 137.8,48.59 198.4,48.59 198.4,38.25 148.7,38.25 148.7,34.66 137.8,34.66 z M 42.19,69.72 C 41.32,69.72 40.71,69.89 40.41,70.19 40.1,70.49 39.97,71.03 39.97,71.84 L 39.97,76.56 C 39.97,77.38 40.1,77.93 40.41,78.22 40.71,78.52 41.32,78.66 42.19,78.66 L 48.72,78.66 C 49.59,78.66 50.19,78.52 50.5,78.22 50.8,77.93 50.97,77.38 50.97,76.56 L 50.97,71.84 C 50.97,71.03 50.8,70.49 50.5,70.19 50.19,69.89 49.59,69.72 48.72,69.72 L 42.19,69.72 z M 64.37,69.72 64.37,78.66 66.25,78.66 66.25,73.84 C 66.25,73.66 66.23,73.43 66.22,73.19 66.2,72.94 66.18,72.69 66.16,72.41 66.26,72.53 66.38,72.67 66.5,72.78 66.62,72.89 66.75,73.01 66.91,73.16 L 73.47,78.66 74.88,78.66 74.88,69.72 73.03,69.72 73.03,74.41 C 73.03,74.52 73.05,74.7 73.06,74.91 73.07,75.11 73.09,75.47 73.12,75.97 72.99,75.81 72.82,75.66 72.66,75.5 72.49,75.35 72.31,75.18 72.09,75 L 65.81,69.72 64.37,69.72 z M 88.53,69.72 88.53,78.66 97.31,78.66 97.31,77 90.59,77 90.59,69.72 88.53,69.72 z M 109.4,69.72 109.4,78.66 111.5,78.66 111.5,69.72 109.4,69.72 z M 125.1,69.72 125.1,78.66 127,78.66 127,73.84 C 127,73.66 127,73.43 126.9,73.19 126.9,72.94 126.9,72.69 126.9,72.41 127,72.53 127.1,72.67 127.2,72.78 127.3,72.89 127.5,73.01 127.6,73.16 L 134.2,78.66 135.6,78.66 135.6,69.72 133.8,69.72 133.8,74.41 C 133.8,74.52 133.8,74.7 133.8,74.91 133.8,75.11 133.8,75.47 133.8,75.97 133.7,75.81 133.6,75.66 133.4,75.5 133.2,75.35 133,75.18 132.8,75 L 126.5,69.72 125.1,69.72 z M 149.3,69.72 149.3,78.66 158.5,78.66 158.5,77 151.3,77 151.3,74.78 155.4,74.78 155.4,73.25 151.3,73.25 151.3,71.25 158.4,71.25 158.4,69.72 149.3,69.72 z M 42.03,71.31 48.87,71.31 48.87,77 42.03,77 42.03,71.31 z" /></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,13 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 120" width="180" height="120">
<title>seafile</title>
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,114.369,-177.525,0,89.989,2.834)">
<stop offset="0" stop-color="#fad956"/>
<stop offset="1" stop-color="#ffa10f"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
</style>
<path class="s0" d="m1.2 52.8c0-3 2.4-5.4 5.4-5.4 1.4 0 2.7 0.6 3.6 1.5q0-0.7 0-1.4c0-9.9 8-17.9 17.9-17.9 2.5 0 4.9 0.5 7.1 1.5q0-0.8 0-1.5c0-14.8 12-26.8 26.8-26.8 14.7 0 26.6 11.9 26.8 26.6-4.8 4.2-8.7 9.6-11.2 15.7-4.8-3-10.4-4.8-16.5-4.8-12.4 0-23.2 7.1-28.3 17.8h-19.1-7.1c-3 0-5.4-2.4-5.4-5.3zm141.2-16c-6.6-6.7-15.8-10.8-25.9-10.8-18.5 0-33.8 13.7-36.3 31.5-4.5-6.1-11.8-10-20-10-13.8 0-25 11.2-25 25 0 4 0.9 7.8 2.6 11.2-8.7 1.7-15.1 8.5-15.1 16.5 0 9.4 8.8 17 19.7 17 4.7 0 9.1-1.5 12.6-4l40.2-39.5c4.4-4.1 10.3-6.6 16.8-6.6 13.6 0 24.7 10.9 25.1 24.4q0 0-0.1-0.1c0.2 4-1.8 8.1-5.7 10.3-5.3 3.1-12 1.4-15-3.7-2.9-5.1-1-11.7 4.4-14.8q1.9-1.1 3.9-1.4-1.8-0.4-3.6-0.4c-9.9 0-17.9 8-17.9 17.9 0 9.9 8 17.9 17.9 17.9q0.6 0 1.3-0.1l0.5-0.1h35.1v0.2c10.7-0.5 20.9-10.4 20.9-22.5 0-12.3-10.6-22.4-22.9-22.4q-0.1 0-0.1 0c-2 3.6-4.4 5.7-7.1 7.9 2.8-5.2 4.5-11.2 4.5-17.6-0.1-10.1-4.2-19.2-10.8-25.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -88,6 +88,8 @@
"useRecoveryKey": "Usa la clau de recuperació",
"incorrectPasswordTitle": "Contrasenya incorrecta",
"welcomeBack": "Benvingut de nou!",
"emailAlreadyRegistered": "El correu electrònic ja està registrat.",
"emailNotRegistered": "El correu electrònic no està registrat.",
"madeWithLoveAtPrefix": "fet amb ❤️ a ",
"supportDevs": "Subscriu-te a <bold-green>ente</bold-green> per donar-nos suport",
"supportDiscount": "Usa el codi de descompte \"AUTH\" per obtenir un 10% de descompte el primer any",
@@ -502,5 +504,13 @@
"deselectAll": "Desselecciona-ho tot",
"selectAll": "Seleccionar-ho tot",
"deleteDuplicates": "Elimina duplicats",
"plainHTML": "HTML pla"
"plainHTML": "HTML pla",
"tellUsWhatYouThink": "Digueu-nos què us sembla",
"dropReview": "Deixa una ressenya a l'App/Play Store",
"supportEnte": "Donar suport a <bold-green>ente</bold-green>",
"giveUsAStarOnGithub": "Dona'ns una estrella a Github",
"free5GB": "5 GB gratuïts a <bold-green>ente</bold-green> Photos",
"loginWithAuthAccount": "Inicieu sessió amb el vostre compte Auth",
"freeStorageOffer": "10% de descompte a <bold-green>ente</bold-green> photos",
"freeStorageOfferDescription": "Utilitzeu el codi \"AUTH\" per obtenir un 10% de descompte el primer any"
}

View File

@@ -88,6 +88,8 @@
"useRecoveryKey": "Wiederherstellungsschlüssel verwenden",
"incorrectPasswordTitle": "Falsches Passwort",
"welcomeBack": "Willkommen zurück!",
"emailAlreadyRegistered": "E-Mail ist bereits registriert.",
"emailNotRegistered": "E-Mail-Adresse nicht registriert.",
"madeWithLoveAtPrefix": "gemacht mit ❤️ bei ",
"supportDevs": "Bei <bold-green>ente</bold-green> registrieren, um das Projekt zu unterstützen",
"supportDiscount": "Benutzen Sie den Rabattcode \"AUTH\" für 10 % Rabatt im ersten Jahr",
@@ -255,6 +257,8 @@
"areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?",
"yesLogout": "Ja ausloggen",
"exit": "Schließen",
"theme": "Theme",
"systemTheme": "System",
"verifyingRecoveryKey": "Verifiziere Wiederherstellungsschlüssel...",
"recoveryKeyVerified": "Wiederherstellungsschlüssel verifiziert",
"recoveryKeySuccessBody": "Großartig! Ihr Wiederherstellungsschlüssel ist gültig. Vielen Dank für die Verifizierung.\n\nBitte denken sie daran, dass sie ihren Wiederherstellungsschlüssel sicher aufbewahren.",
@@ -325,6 +329,7 @@
}
}
},
"manualSort": "Benutzerdefiniert",
"activeSessions": "Aktive Sitzungen",
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
"thisWillLogYouOutOfThisDevice": "Dadurch wirst du von diesem Gerät abgemeldet!",
@@ -478,5 +483,9 @@
"setNewPin": "Neue PIN festlegen",
"importFailureDescNew": "Die ausgewählte Datei konnte nicht verarbeitet werden.",
"appLockNotEnabled": "App-Sperre nicht aktiviert",
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre"
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre",
"duplicateCodes": "Doppelte Codes",
"noDuplicates": "✨ Keine Duplikate",
"deselectAll": "Alle abwählen",
"selectAll": "Alles auswählen"
}

View File

@@ -504,5 +504,12 @@
"deselectAll": "Deseleccionar todo",
"selectAll": "Seleccionar todo",
"deleteDuplicates": "Eliminar duplicados",
"plainHTML": "HTML plano"
"plainHTML": "HTML plano",
"tellUsWhatYouThink": "Cuéntanos cuál es su opinión",
"dropReview": "Danos una reseña en la App/Play Store",
"supportEnte": "Apoya a <bold-green>ente</bold-green>",
"giveUsAStarOnGithub": "Danos una estrella en GitHub",
"free5GB": "5 GB gratis en <bold-green>ente</bold-green> Fotos",
"freeStorageOffer": "10% de descuento en <bold-green>ente</bold-green> fotos",
"freeStorageOfferDescription": "Usa el cupón \"AUTH\" para obtener un 10% de descuento en el primer año"
}

View File

@@ -504,5 +504,13 @@
"deselectAll": "Összes kijelölés megszüntetése",
"selectAll": "Összes kijelölése",
"deleteDuplicates": "Ismétlődések törlése",
"plainHTML": "Sima HTML kód"
"plainHTML": "Sima HTML kód",
"tellUsWhatYouThink": "Mondja el mit gondol",
"dropReview": "Írjon véleményt az App/Play Store-ban",
"supportEnte": "Támogassa <bold-green>ente <bold-green>",
"giveUsAStarOnGithub": "Adj nekünk egy csillagot a Githubon",
"free5GB": "5GB ingyen <bold-green>ente <bold-green> Photos",
"loginWithAuthAccount": "Jelentkezzen be Auth fiókjával",
"freeStorageOffer": "10% kedvezmény on <bold-green>ente<bold-green> photos",
"freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben"
}

View File

@@ -499,7 +499,18 @@
"appLockOfflineModeWarning": "バックアップなしで進むことを選択しました。アプリロックを忘れると、データにアクセスできなくなります。",
"duplicateCodes": "重複コード",
"noDuplicates": "✨ 重複なし",
"youveNoDuplicateCodesThatCanBeCleared": "削除できる重複コードはありません",
"deduplicateCodes": "重複コード",
"deselectAll": "すべての選択を解除",
"selectAll": "すべて選択",
"deleteDuplicates": "重複を削除",
"plainHTML": "Plain HTML",
"tellUsWhatYouThink": "ご意見をお聞かせください",
"loginWithAuthAccount": "認証アカウントでログイン"
"dropReview": "App/Playストアにレビューを投稿する",
"supportEnte": "<bold-green>ente</bold-green>をサポートする",
"giveUsAStarOnGithub": "Githubで星をつける",
"free5GB": "<bold-green>ente</bold-green>フォトで5GB無料",
"loginWithAuthAccount": "認証アカウントでログイン",
"freeStorageOffer": "<bold-green>ente</bold-green>の写真が10%オフ",
"freeStorageOfferDescription": "クーポンコード \"AUTH\" の使用で初年度が10%オフになります"
}

View File

@@ -1 +1,28 @@
{}
{
"blog": "ബ്ലോഗ്",
"verifyPassword": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക",
"recreatePassword": "പാസ്‌വേഡ് പുനഃസൃഷ്ടിക്കുക",
"incorrectPasswordTitle": "തെറ്റായ പാസ്‌വേഡ്",
"welcomeBack": "വീണ്ടും സ്വാഗതം!",
"emailAlreadyRegistered": "ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.",
"emailNotRegistered": "ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.",
"changeEmail": "ഇമെയിൽ മാറ്റുക",
"changePassword": "പാസ്സ്‌വേർഡ് മാറ്റുക",
"ok": "ശരി",
"cancel": "റദ്ദാക്കുക",
"yes": "അതെ",
"no": "അല്ല",
"email": "ഇമെയിൽ",
"somethingWentWrongMessage": "എന്തോ കുഴപ്പമുണ്ടായി, ദയവായി വീണ്ടും ശ്രമിക്കുക",
"inFamilyPlanMessage": "നിങ്ങൾ ഒരു ഫാമിലി പ്ലാനിലാണ്!",
"scan": "സ്കാൻ ചെയ്യുക",
"scanACode": "കോഡ് സ്കാൻ ചെയ്യുക",
"verify": "പരിശോധിക്കുക",
"verifyEmail": "ഇമെയിൽ സ്ഥിരീകരിക്കുക",
"enterCodeHint": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക",
"twoFactorAuthTitle": "ടു-ഫാക്ടർ ആധികാരികത",
"createNewAccount": "പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക",
"confirmPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
"language": "ഭാഷ",
"security": "സുരക്ഷ"
}

View File

@@ -362,8 +362,18 @@ const createMainWindow = () => {
// do it (Step 2) unconditionally (i.e., on macOS too).
//
// https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar
//
// Note that by default on Windows, the color of the WCO title bar
// overlay (three buttons - minimize, maximize, close - on the top
// right) is static, and unlike Linux, doesn't adapt to the theme /
// content. Explicitly choosing a dark background, while it won't work
// always (if the user's theme is light), is better than picking a light
// background since the main image viewer is always dark.
titleBarStyle: "hidden",
titleBarOverlay: true,
titleBarOverlay:
process.platform == "win32"
? { color: "black", symbolColor: "#cdcdcd" }
: true,
// The color to show in the window until the web content gets loaded.
// https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property
//

View File

@@ -138,6 +138,10 @@ export const sidebar = [
text: "Machine Learning",
link: "/photos/faq/machine-learning",
},
{
text: "Video Streaming",
link: "/photos/faq/video-streaming",
},
],
},
{

View File

@@ -0,0 +1,63 @@
---
title: Video Streaming FAQ
description:
Frequently asked questions about Ente's Video Streaming feature
---
# Video Streaming
> [!NOTE]
>
> Video streaming is available in beta on mobile apps starting v0.9.98.
### How to enable video streaming?
- Open Settings -> General -> Advanced
- Switch on the toggle for `Video streaming`
### What happens when I enable video streaming?
Enabling video streaming will start processing videos captured in the last 30
days, generating streams for each. Both local and remote videos will be
processed, so this may consume bandwidth for downloading of remote files and
uploading of the generated streams.
### How can I view video streams?
Settings -> Backup > Backup status will show details regarding the processing
status for videos. Processed videos will have a green play button next to them.
You can open these videos by tapping on them.
Processed videos will show a `Play stream` button, clicking which will load and
play the stream.
Clicking on the `Info` icon within the original video will show details
about the generated stream.
### What is a stream?
Stream is an encrypted HLS file with an `.m3u8` playlist that helps play a video
with support for seeking **without** downloading the full file.
Currently it converts videos into `720p` with `2mbps` bitrate in `H.264` format.
The generated stream is single blob (encrypted with AES) while the playlist file
(`.m3u8`) is another blob (encrypted using XChaCha20).
We cannot read the contents, duration or the number of chunks within the
generated stream.
### Will streams consume space in my storage?
While this feature is in beta, we will not count the storage consumed by your
streams against your storage quota. This may change in the future. If it does,
we will provide an option to opt-in to one of the following:
1. Original videos only
2. Compressed streams only
3. Both
### Something doesn't seem right, what to do?
As video streaming is still in beta, some things might not work correctly.
Please create a thread within the `#feedback` channel on
[Discord](https://discord.com/channels/948937918347608085/1121126215995113552)
or reach out to [support@ente.io](mailto:support@ente.io).

View File

@@ -43,6 +43,10 @@ need to disable this "Optimize battery usage" mode in the system settings for
Ente if you wish for Ente to automatically back up your photos in the
background.
On Android versions 15 and later, if an app is in private space and the private
space is locked, Android doesnt allow the app to run any background processes.
As a result, background sync will not work.
### Desktop
In addition to our mobile apps, the background sync also works on our desktop

View File

@@ -450,84 +450,84 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2
battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57
battery_info: a06b00c06a39bc94c92beebf600f1810cb6c8c87
connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b
ffmpeg_kit_flutter_full_gpl: 8d15c14c0c3aba616fac04fe44b3d27d02e3c330
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
ffmpeg_kit_flutter_full_gpl: ce18b888487c05c46ed252cd2e7956812f2e3bd1
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
firebase_core: 2bedc3136ec7c7b8561c6123ed0239387b53f2af
firebase_messaging: 15d114e1a41fc31e4fbabcd48d765a19eec94a38
firebase_core: 085320ddfaacb80d1a96eac3a87857afcc150db1
firebase_messaging: d398edc15fe825f832836e74f6ac61e8cd2f3ad3
FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
flutter_email_sender: cd533cdc7ea5eda6fabb2c7f78521c71207778a4
flutter_image_compress: 4b058288a81f76e5e80340af37c709abafff34c4
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
flutter_sodium: 152647449ba89a157fd48d7e293dcd6d29c6ab0e
fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
in_app_purchase_storekit: e126ef1b89e4a9fdf07e28f005f82632b4609437
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
media_extension: 6d30dc1431ebaa63f43c397c37917b1a0a597a4c
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
media_extension: a1fec16ee9c8241a6aef9613578ebf097d6c5e64
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1
motionphoto: 584b43031ead3060225cdff08fa49818879801d2
move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: d12af78a1a4a8cf09775a5177d5b392def6fd23c
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0
open_mail_app: 06d5a4162866388a92b1df3deb96e56be20cf45c
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
receive_sharing_intent: df9c334dc9feadcbd3266e5cb49c8443405e1c9f
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
receive_sharing_intent: f6a12b7e8f7ed745f61c982de8a65de88db44a44
screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
sentry_flutter: 0a211008f52553ba5dd81ceb71f48d78f0f1f6ab
share_plus: 011d6fb4f9d2576b83179a3a5c5e323202cdabcf
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 44bb54cc302bff1fbe5752293aba1820b157cf1c
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
sqlite3_flutter_libs: 9379996d65aa23dcda7585a5b58766cebe0aa042
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
ua_client_hints: 46bb5817a868f9e397c0ba7e3f2f5c5d90c35156
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
ua_client_hints: 0b48eae1134283f5b131ee0871fa878377f07a01
uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
volume_controller: ca1cde542ee70fad77d388f82e9616488110942b
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd

View File

@@ -18,6 +18,7 @@ import 'package:photos/core/error-reporting/tunneled_transport.dart';
import "package:photos/core/errors.dart";
import 'package:photos/models/typedefs.dart';
import "package:photos/utils/device_info.dart";
import "package:photos/utils/ram_check_util.dart";
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -205,6 +206,12 @@ class SuperLogging {
}),
);
unawaited(
checkDeviceTotalRAM().then((ram) {
if (ram != null) $.info("Device RAM: ${ram}MB");
}),
);
if (appConfig.body == null) return;
if (enable && sentryIsEnabled) {

View File

@@ -251,20 +251,20 @@ class CollectionsDB {
Map<String, dynamic> _getRowForCollection(Collection collection) {
final row = <String, dynamic>{};
row[columnID] = collection.id;
row[columnOwner] = collection.owner!.toJson();
row[columnOwner] = collection.owner.toJson();
row[columnEncryptedKey] = collection.encryptedKey;
row[columnKeyDecryptionNonce] = collection.keyDecryptionNonce;
row[columnName] = collection.name;
row[columnEncryptedName] = collection.encryptedName;
row[columnNameDecryptionNonce] = collection.nameDecryptionNonce;
row[columnType] = Collection.typeToString(collection.type);
row[columnType] = typeToString(collection.type);
row[columnEncryptedPath] = collection.attributes.encryptedPath;
row[columnPathDecryptionNonce] = collection.attributes.pathDecryptionNonce;
row[columnVersion] = collection.attributes.version;
row[columnSharees] =
json.encode(collection.sharees?.map((x) => x?.toMap()).toList());
json.encode(collection.sharees.map((x) => x.toMap()).toList());
row[columnPublicURLs] =
json.encode(collection.publicURLs?.map((x) => x?.toMap()).toList());
json.encode(collection.publicURLs.map((x) => x.toMap()).toList());
row[columnUpdationTime] = collection.updationTime;
if (collection.isDeleted) {
row[columnIsDeleted] = _sqlBoolTrue;
@@ -290,7 +290,7 @@ class CollectionsDB {
row[columnName],
row[columnEncryptedName],
row[columnNameDecryptionNonce],
Collection.typeFromString(row[columnType]),
typeFromString(row[columnType]),
CollectionAttributes(
encryptedPath: row[columnEncryptedPath],
pathDecryptionNonce: row[columnPathDecryptionNonce],

View File

@@ -1733,6 +1733,7 @@ class FilesDB {
Future<List<EnteFile>> getAllFilesAfterDate({
required FileType fileType,
required DateTime beginDate,
required int userID,
}) async {
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
@@ -1741,6 +1742,7 @@ class FilesDB {
WHERE $columnFileType = ?
AND $columnCreationTime > ?
AND $columnUploadedFileID != -1
AND $columnOwnerID = $userID
ORDER BY $columnCreationTime DESC
''',
[getInt(fileType), beginDate.microsecondsSinceEpoch],

View File

@@ -322,22 +322,20 @@ class _AddContactPage extends State<AddContactPage> {
final int ownerID = Configuration.instance.getUserID()!;
existingEmails.add(Configuration.instance.getEmail()!);
for (final c in CollectionsService.instance.getActiveCollections()) {
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null &&
u.email.isNotEmpty &&
!existingEmails.contains(u.email)) {
existingEmails.add(u.email);
suggestedUsers.add(u);
}
}
} else if (c.owner != null &&
c.owner!.id != null &&
c.owner!.email.isNotEmpty &&
!existingEmails.contains(c.owner!.email)) {
existingEmails.add(c.owner!.email);
suggestedUsers.add(c.owner!);
} else if (c.owner.id != null &&
c.owner.email.isNotEmpty &&
!existingEmails.contains(c.owner.email)) {
existingEmails.add(c.owner.email);
suggestedUsers.add(c.owner);
}
}
final cachedUserDetails = UserService.instance.getCachedUserDetails();

View File

@@ -45,7 +45,7 @@ class CreateRequest {
map['keyDecryptionNonce'] = keyDecryptionNonce;
map['encryptedName'] = encryptedName;
map['nameDecryptionNonce'] = nameDecryptionNonce;
map['type'] = Collection.typeToString(type);
map['type'] = typeToString(type);
if (attributes != null) {
map['attributes'] = attributes!.toMap();
}

View File

@@ -8,7 +8,7 @@ import "package:photos/models/metadata/common_keys.dart";
class Collection {
final int id;
final User? owner;
final User owner;
final String encryptedKey;
final String? keyDecryptionNonce;
@Deprecated("Use collectionName instead")
@@ -20,8 +20,8 @@ class Collection {
final String? nameDecryptionNonce;
final CollectionType type;
final CollectionAttributes attributes;
final List<User?>? sharees;
final List<PublicURL?>? publicURLs;
final List<User> sharees;
final List<PublicURL> publicURLs;
final int updationTime;
final bool isDeleted;
@@ -95,12 +95,12 @@ class Collection {
// hasLink returns true if there's any link attached to the collection
// including expired links
bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
bool get hasLink => publicURLs.isNotEmpty;
bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
// hasSharees returns true if the collection is shared with other ente users
bool get hasSharees => sharees != null && sharees!.isNotEmpty;
bool get hasSharees => sharees.isNotEmpty;
bool get isPinned => (magicMetadata.order ?? 0) != 0;
@@ -121,52 +121,43 @@ class Collection {
}
List<User> getSharees() {
final List<User> result = [];
if (sharees == null) {
return result;
}
for (final User? u in sharees!) {
if (u != null) {
result.add(u);
}
}
return result;
return sharees;
}
bool isOwner(int userID) {
return (owner?.id ?? 0) == userID;
return (owner.id ?? -100) == userID;
}
bool isDownloadEnabledForPublicLink() {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableDownload ?? true;
return publicURLs.first.enableDownload;
}
bool isCollectEnabledForPublicLink() {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableCollect ?? false;
return publicURLs.first.enableCollect;
}
bool get isJoinEnabled {
if (publicURLs == null || publicURLs!.isEmpty) {
if (publicURLs.isEmpty) {
return false;
}
return publicURLs?.first?.enableJoin ?? false;
return publicURLs.first.enableJoin;
}
CollectionParticipantRole getRole(int userID) {
if (isOwner(userID)) {
return CollectionParticipantRole.owner;
}
if (sharees == null) {
if (sharees.isEmpty) {
return CollectionParticipantRole.unknown;
}
for (final User? u in sharees!) {
if (u != null && u.id == userID) {
for (final User u in sharees) {
if (u.id == userID) {
if (u.isViewer) {
return CollectionParticipantRole.viewer;
} else if (u.isCollaborator) {
@@ -185,40 +176,8 @@ class Collection {
}
void updateSharees(List<User> newSharees) {
sharees?.clear();
sharees?.addAll(newSharees);
}
static CollectionType typeFromString(String type) {
switch (type) {
case "folder":
return CollectionType.folder;
case "favorites":
return CollectionType.favorites;
case "uncategorized":
return CollectionType.uncategorized;
case "album":
return CollectionType.album;
case "unknown":
return CollectionType.unknown;
}
debugPrint("unexpected collection type $type");
return CollectionType.unknown;
}
static String typeToString(CollectionType type) {
switch (type) {
case CollectionType.folder:
return "folder";
case CollectionType.favorites:
return "favorites";
case CollectionType.album:
return "album";
case CollectionType.uncategorized:
return "uncategorized";
case CollectionType.unknown:
return "unknown";
}
sharees.clear();
sharees.addAll(newSharees);
}
Collection copyWith({
@@ -303,6 +262,38 @@ enum CollectionType {
unknown,
}
CollectionType typeFromString(String type) {
switch (type) {
case "folder":
return CollectionType.folder;
case "favorites":
return CollectionType.favorites;
case "uncategorized":
return CollectionType.uncategorized;
case "album":
return CollectionType.album;
case "unknown":
return CollectionType.unknown;
}
debugPrint("unexpected collection type $type");
return CollectionType.unknown;
}
String typeToString(CollectionType type) {
switch (type) {
case CollectionType.folder:
return "folder";
case CollectionType.favorites:
return "favorites";
case CollectionType.album:
return "album";
case CollectionType.uncategorized:
return "uncategorized";
case CollectionType.unknown:
return "unknown";
}
}
extension CollectionTypeExtn on CollectionType {
bool get canDelete =>
this != CollectionType.favorites && this != CollectionType.uncategorized;

View File

@@ -139,7 +139,7 @@ class CollectionsService {
}
}
// remove reference for incoming collections when unshared/deleted
if (collection.isDeleted && ownerID != collection.owner?.id) {
if (collection.isDeleted && ownerID != collection.owner.id) {
await _db.deleteCollection(collection.id);
} else {
// keep entry for deletedCollection as collectionKey may be used during
@@ -394,7 +394,7 @@ class CollectionsService {
final List<Collection> collections =
getCollectionsForUI(includedShared: true);
for (final c in collections) {
if (c.owner!.id == Configuration.instance.getUserID()) {
if (c.owner.id == Configuration.instance.getUserID()) {
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
outgoing.add(c);
} else if (c.isQuickLinkCollection()) {
@@ -472,8 +472,8 @@ class CollectionsService {
if (collectionID != null) {
final Collection? collection = getCollectionByID(collectionID);
if (collection != null) {
if (collection.owner?.id == userID) {
_cachedUserIdToUser[userID] = collection.owner!;
if (collection.owner.id == userID) {
_cachedUserIdToUser[userID] = collection.owner;
} else {
final matchingUser = collection.getSharees().firstWhereOrNull(
(u) => u.id == userID,
@@ -698,7 +698,7 @@ class CollectionsService {
);
final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey);
Uint8List? collectionKey;
if (collection.owner?.id == _config.getUserID()) {
if (collection.owner.id == _config.getUserID()) {
// If the collection is owned by the user, decrypt with the master key
if (_config.getKey() == null) {
// Possible during AppStore account migration, where SecureStorage
@@ -767,7 +767,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id != ownerID) {
if (collection.owner.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -798,7 +798,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/magic-metadata",
data: params,
data: params.toJson(),
);
// update the local information so that it's reflected on UI
collection.mMdEncodedJson = jsonEncode(jsonToUpdate);
@@ -826,7 +826,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id != ownerID) {
if (collection.owner.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -857,7 +857,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/public-magic-metadata",
data: params,
data: params.toJson(),
);
// update the local information so that it's reflected on UI
collection.mMdPubEncodedJson = jsonEncode(jsonToUpdate);
@@ -885,7 +885,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner?.id == ownerID) {
if (collection.owner.id == ownerID) {
throw AssertionError("cannot modify sharee settings for albums owned "
"by you");
}
@@ -917,7 +917,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/sharee-magic-metadata",
data: params,
data: params.toJson(),
);
// update the local information so that it's reflected on UI
collection.sharedMmdJson = jsonEncode(jsonToUpdate);
@@ -952,7 +952,7 @@ class CollectionsService {
"enableJoin": true,
},
);
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
@@ -980,8 +980,8 @@ class CollectionsService {
data: json.encode(prop),
);
// remove existing url information
collection.publicURLs?.clear();
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs.clear();
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
@@ -1003,7 +1003,7 @@ class CollectionsService {
await _enteDio.delete(
"/collections/share-url/" + collection.id.toString(),
);
collection.publicURLs?.clear();
collection.publicURLs.clear();
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(

View File

@@ -230,7 +230,7 @@ class FavoritesService {
if (_cachedFavoritesCollectionID == null) {
final collections = _collectionsService.getActiveCollections();
for (final collection in collections) {
if (collection.owner!.id == _config.getUserID() &&
if (collection.owner.id == _config.getUserID() &&
collection.type == CollectionType.favorites) {
_cachedFavoritesCollectionID = collection.id;
return collection;

View File

@@ -32,7 +32,7 @@ extension HiddenService on CollectionsService {
final int userID = config.getUserID()!;
final allDefaultHidden = collectionIDToCollections.values
.where(
(element) => element.isDefaultHidden() && element.owner!.id == userID,
(element) => element.isDefaultHidden() && element.owner.id == userID,
)
.toList();
@@ -101,7 +101,7 @@ extension HiddenService on CollectionsService {
collectionIDToCollections.values.firstWhereOrNull(
(element) =>
element.type == CollectionType.uncategorized &&
element.owner!.id == userID,
element.owner.id == userID,
);
if (matchedCollection != null) {
cachedUncategorizedCollection = matchedCollection;
@@ -166,7 +166,9 @@ extension HiddenService on CollectionsService {
await dialog.hide();
} on AssertionError catch (e) {
await dialog.hide();
unawaited(showErrorDialog(context, S.of(context).oops, e.message as String));
unawaited(
showErrorDialog(context, S.of(context).oops, e.message as String),
);
return false;
} catch (e, s) {
_logger.severe("Could not hide", e, s);

View File

@@ -193,6 +193,28 @@ class MLIndexingIsolate extends SuperIsolate {
}
}
/// WARNING: This method is only for debugging purposes. It should not be used in production.
Future<void> debugLoadSingleModel(MLModels model) {
return _initModelLock.synchronized(() async {
final modelInstance = model.model;
if (modelInstance.isInitialized) {
_logger.info("Model ${model.name} already loaded");
return;
}
final modelName = modelInstance.modelName;
final modelPath = await modelInstance.downloadModelSafe();
if (modelPath == null) {
_logger.severe("Could not download model, no wifi");
return;
}
final address = await runInIsolate(IsolateOperation.loadModel, {
"modelName": modelName,
"modelPath": modelPath,
}) as int;
modelInstance.storeSessionAddress(address);
});
}
Future<void> cleanupLocalIndexingModels({bool delete = false}) async {
if (!areModelsDownloaded) return;
await _releaseModels();

View File

@@ -135,22 +135,6 @@ class PreviewVideoStore {
}
}
final fileSize = file.lengthSync();
FFProbeProps? props;
if (fileSize <= 10 * 1024 * 1024) {
props = await getVideoPropsAsync(file);
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
final codec = videoData["codec_name"]?.toString().toLowerCase();
final codecIsH264 = codec?.contains("h264") ?? false;
if (codecIsH264) {
_items.removeWhere((key, value) => value.file == enteFile);
Bus.instance.fire(PreviewUpdatedEvent(_items));
return;
}
}
if (uploadingFileId >= 0) {
_items[enteFile.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.inQueue,
@@ -174,7 +158,8 @@ class PreviewVideoStore {
);
Bus.instance.fire(PreviewUpdatedEvent(_items));
props ??= await getVideoPropsAsync(file);
final props = await getVideoPropsAsync(file);
final fileSize = enteFile.fileSize ?? file.lengthSync();
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
@@ -207,13 +192,11 @@ class PreviewVideoStore {
final codecIsH264 = codec?.contains("h264") ?? false;
if (bitrate != null && bitrate <= 4000 * 1000 && codecIsH264) {
// create playlist without compression, as is
session = await FFmpegKit.execute(
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 ' // Adjust metadata if needed
'-c:v copy ' // Copy the original video codec
'-c:a copy ' // Copy the original audio codec
'-f hls -hls_time 10 -hls_flags single_file '
'-metadata:s:v:0 rotate=0 '
'-c:v copy -c:a copy '
'-f hls -hls_time 2 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -221,16 +204,14 @@ class PreviewVideoStore {
codec != null &&
bitrate <= 2000 * 1000 &&
!codecIsH264) {
// compress video with crf=21, h264 no change in resolution or frame rate,
// just change color scheme
session = await FFmpegKit.execute(
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 ' // Keep rotation metadata
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' // Adjust color scheme
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' // Set color profile to BT.709
'-c:v libx264 -crf 21 -preset medium ' // Compress with CRF=21 using H.264
'-c:a copy ' // Keep original audio
'-f hls -hls_time 10 -hls_flags single_file '
'-metadata:s:v:0 rotate=0 '
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
'-c:v libx264 -crf 23 -preset medium '
'-c:a copy '
'-f hls -hls_time 2 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -241,8 +222,8 @@ class PreviewVideoStore {
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 '
'-vf "scale=-2:720,fps=30" '
'-c:v libx264 -b:v 2000k -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -254,8 +235,8 @@ class PreviewVideoStore {
'-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
'-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" '
'-c:v libx264 -b:v 2000k -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -601,21 +582,49 @@ class PreviewVideoStore {
final files = await FilesDB.instance.getAllFilesAfterDate(
fileType: FileType.video,
beginDate: cutoff,
userID: Configuration.instance.getUserID()!,
);
final previewIds = FileDataService.instance.previewIds;
final allFiles = files
.where((file) => previewIds?[file.uploadedFileID] == null)
.toList();
.sorted((a, b) {
// put higher duration videos last
final first = a.duration == null || a.duration! >= 10 * 60 ? 1 : 0;
final second = b.duration == null || b.duration! >= 10 * 60 ? 1 : 0;
return first.compareTo(second);
}).toList();
// set all video status to be in queue
for (final file in allFiles) {
_items[file.uploadedFileID!] = PreviewItem(
for (final enteFile in allFiles) {
final fileSize = enteFile.fileSize;
FFProbeProps? props;
if (fileSize != null && fileSize <= 10 * 1024 * 1024) {
final file = await getFile(enteFile, isOrigin: true);
if (file != null) {
props = await getVideoPropsAsync(file);
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
final codec = videoData["codec_name"]?.toString().toLowerCase();
final codecIsH264 = codec?.contains("h264") ?? false;
if (codecIsH264) {
_items.removeWhere((key, value) => value.file == enteFile);
Bus.instance.fire(PreviewUpdatedEvent(_items));
continue;
}
}
}
_items[enteFile.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.inQueue,
file: file,
collectionID: file.collectionID ?? 0,
file: enteFile,
collectionID: enteFile.collectionID ?? 0,
);
}
Bus.instance.fire(PreviewUpdatedEvent(_items));
final file = allFiles.first;

View File

@@ -1311,34 +1311,30 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
}
}
}
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
// Add owners of collections shared with user
if (!existingEmails.contains(c.owner!.email)) {
relevantUsers.add(c.owner!);
existingEmails.add(c.owner!.email);
if (!existingEmails.contains(c.owner.email)) {
relevantUsers.add(c.owner);
existingEmails.add(c.owner.email);
}
// Add collaborators of collections shared with user where user is a
// viewer or a collaborator
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
for (final User u in c.sharees) {
if (u.id != null &&
u.email.isNotEmpty &&
u.email == ownerEmail &&
(u.isCollaborator || u.isViewer)) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
@@ -1392,32 +1388,28 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}
}
}
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
// Add owners of collections shared with user
if (!emailIDs.contains(c.owner!.email)) {
emailIDs.add(c.owner!.email);
if (!emailIDs.contains(c.owner.email)) {
emailIDs.add(c.owner.email);
}
// Add collaborators of collections shared with user where user is a
// viewer or a collaborator
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
for (final User u in c.sharees) {
if (u.id != null &&
u.email.isNotEmpty &&
u.email == ownerEmail &&
(u.isCollaborator || u.isViewer)) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}

View File

@@ -147,7 +147,7 @@ extension CollectionFileActions on CollectionActions {
// Newly created collection might not be cached
final Collection? c =
CollectionsService.instance.getCollectionByID(collectionID);
if (c != null && c.owner!.id != currentUserID) {
if (c != null && c.owner.id != currentUserID) {
if (!showProgressDialog) {
dialog = createProgressDialog(
context,

View File

@@ -340,7 +340,7 @@ class CollectionActions {
) async {
final textTheme = getEnteTextTheme(bContext);
final currentUserID = Configuration.instance.getUserID()!;
if (collection.owner!.id != currentUserID) {
if (collection.owner.id != currentUserID) {
throw AssertionError("Can not delete album owned by others");
}
if (collection.hasSharees) {
@@ -495,7 +495,7 @@ class CollectionActions {
bool isHidden = false,
}) async {
final int currentUserID = Configuration.instance.getUserID()!;
final isCollectionOwner = collection.owner!.id == currentUserID;
final isCollectionOwner = collection.owner.id == currentUserID;
final FilesSplit split = FilesSplit.split(
files,
Configuration.instance.getUserID()!,
@@ -631,7 +631,7 @@ class CollectionActions {
if (targetCollection == null ||
(CollectionType.uncategorized == targetCollection.type ||
targetCollection.type == CollectionType.favorites) ||
targetCollection.owner!.id != userID) {
targetCollection.owner.id != userID) {
return false;
}
return true;

View File

@@ -41,7 +41,7 @@ class AlbumRowItemWidget extends StatelessWidget {
final Widget? linkIcon = c.hasLink && isOwner
? Icon(
Icons.link,
color: c.publicURLs!.first!.isExpired ? warning500 : strokeBaseDark,
color: c.publicURLs.first.isExpired ? warning500 : strokeBaseDark,
)
: null;
return GestureDetector(
@@ -115,7 +115,7 @@ class AlbumRowItemWidget extends StatelessWidget {
bottom: 8.0,
),
child: UserAvatarWidget(
c.owner!,
c.owner,
thumbnailView: true,
),
),

View File

@@ -287,8 +287,8 @@ class AlbumVerticalListWidget extends StatelessWidget {
CollectionActions(CollectionsService.instance);
if (collection.hasLink) {
if (collection.publicURLs!.first!.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (collection.publicURLs.first.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,
@@ -334,7 +334,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
context,
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
);
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,
@@ -353,7 +353,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
BuildContext context,
Collection collection,
) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
if (Configuration.instance.getUserID() == collection.owner.id) {
unawaited(
routeToPage(
context,

View File

@@ -117,28 +117,26 @@ class AdvancedSettingsScreen extends StatelessWidget {
},
),
),
if (flagService.internalUser) ...[
const SizedBox(height: 24),
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).videoStreaming,
),
menuItemColor: colorScheme.fillFaint,
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
trailingWidget: ToggleSwitchWidget(
value: () => PreviewVideoStore
.instance.isVideoStreamingEnabled,
onChanged: () async {
final isEnabled = PreviewVideoStore
.instance.isVideoStreamingEnabled;
await PreviewVideoStore.instance
.setIsVideoStreamingEnabled(!isEnabled);
},
),
const SizedBox(height: 24),
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).videoStreaming,
),
],
menuItemColor: colorScheme.fillFaint,
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
trailingWidget: ToggleSwitchWidget(
value: () => PreviewVideoStore
.instance.isVideoStreamingEnabled,
onChanged: () async {
final isEnabled = PreviewVideoStore
.instance.isVideoStreamingEnabled;
await PreviewVideoStore.instance
.setIsVideoStreamingEnabled(!isEnabled);
},
),
),
const SizedBox(
height: 24,
),

View File

@@ -5,6 +5,8 @@ import "package:photos/db/ml/base.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import "package:photos/services/machine_learning/ml_models_overview.dart";
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/buttons/button_widget.dart";
@@ -104,6 +106,90 @@ class _MLUserDeveloperOptionsState extends State<MLUserDeveloperOptions> {
isBottomBorderRadiusRemoved: true,
isGestureDetectorDisabled: true,
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load face detection model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.faceDetection);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load face recognition model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.faceEmbedding);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load clip image model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.clipImageEncoder);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load clip text model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.clipTextEncoder);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SafeArea(
child: SizedBox(
height: 12,

View File

@@ -363,8 +363,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
List<User> _getSuggestedUser() {
final Set<String> existingEmails = {};
for (final User? u in widget.collection.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
for (final User u in widget.collection.sharees) {
if (u.id != null && u.email.isNotEmpty) {
existingEmails.add(u.email);
}
}

View File

@@ -64,11 +64,11 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
@override
Widget build(BuildContext context) {
final isOwner =
widget.collection.owner?.id == Configuration.instance.getUserID();
widget.collection.owner.id == Configuration.instance.getUserID();
final colorScheme = getEnteColorScheme(context);
final currentUserID = Configuration.instance.getUserID()!;
final int participants = 1 + widget.collection.getSharees().length;
final User owner = widget.collection.owner!;
final User owner = widget.collection.owner;
if (owner.id == currentUserID && owner.email == "") {
owner.email = Configuration.instance.getEmail()!;
}
@@ -107,11 +107,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
captionedTextWidget: CaptionedTextWidget(
title: isOwner
? S.of(context).you
: widget.collection.owner != null
? _nameIfAvailableElseEmail(
widget.collection.owner!,
)
: '',
: _nameIfAvailableElseEmail(
widget.collection.owner,
),
makeTextBold: isOwner,
),
leadingIconWidget: UserAvatarWidget(

View File

@@ -47,13 +47,13 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
@override
Widget build(BuildContext context) {
final isCollectEnabled =
widget.collection!.publicURLs?.firstOrNull?.enableCollect ?? false;
widget.collection!.publicURLs.firstOrNull?.enableCollect ?? false;
final isDownloadEnabled =
widget.collection!.publicURLs?.firstOrNull?.enableDownload ?? true;
widget.collection!.publicURLs.firstOrNull?.enableDownload ?? true;
final isPasswordEnabled =
widget.collection!.publicURLs?.firstOrNull?.passwordEnabled ?? false;
widget.collection!.publicURLs.firstOrNull?.passwordEnabled ?? false;
final enteColorScheme = getEnteColorScheme(context);
final PublicURL url = widget.collection!.publicURLs!.firstOrNull!;
final PublicURL url = widget.collection!.publicURLs.firstOrNull!;
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.collection!.id),
);

View File

@@ -72,7 +72,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
bool isCustomLimit = false;
@override
void initState() {
currentDeviceLimit = widget.collection.publicURLs!.first!.deviceLimit;
currentDeviceLimit = widget.collection.publicURLs.first.deviceLimit;
initialDeviceLimit = currentDeviceLimit;
if (!publicLinkDeviceLimits.contains(currentDeviceLimit)) {
isCustomLimit = true;

View File

@@ -61,7 +61,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
@override
Widget build(BuildContext context) {
_sharees = widget.collection.sharees ?? [];
_sharees = widget.collection.sharees;
final bool hasUrl = widget.collection.hasLink;
final children = <Widget>[];
children.add(
@@ -136,7 +136,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
}
final bool hasExpired =
widget.collection.publicURLs?.firstOrNull?.isExpired ?? false;
widget.collection.publicURLs.firstOrNull?.isExpired ?? false;
children.addAll([
const SizedBox(
height: 24,
@@ -166,7 +166,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
CollectionsService.instance.getCollectionKey(widget.collection.id),
);
final String url =
"${widget.collection.publicURLs!.first!.url}#$collectionKey";
"${widget.collection.publicURLs.first.url}#$collectionKey";
children.addAll(
[
MenuItemWidget(

View File

@@ -275,7 +275,7 @@ class _HomeWidgetState extends State<HomeWidget> {
final existingCollection =
CollectionsService.instance.getCollectionByID(collection.id);
if (collection.owner!.id! == Configuration.instance.getUserID() ||
if (collection.isOwner(Configuration.instance.getUserID() ?? -1) ||
(existingCollection != null && !existingCollection.isDeleted)) {
await routeToPage(
context,
@@ -286,8 +286,8 @@ class _HomeWidgetState extends State<HomeWidget> {
return;
}
final dialog = createProgressDialog(context, "Loading...");
final publicUrl = collection.publicURLs![0];
if (!publicUrl!.enableDownload) {
final publicUrl = collection.publicURLs[0];
if (!publicUrl.enableDownload) {
await showErrorDialog(
context,
context.l10n.canNotOpenTitle,

View File

@@ -109,7 +109,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
style: textTheme.smallMuted,
),
c.hasLink
? (c.publicURLs!.first!.isExpired
? (c.publicURLs.first.isExpired
? Icon(
Icons.link_outlined,
color: colorScheme.warning500,

View File

@@ -804,7 +804,7 @@ class _FileSelectionActionsWidgetState
.getCollectionKey(_cachedCollectionForSharedLink!.id),
);
final String url =
"${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey";
"${_cachedCollectionForSharedLink!.publicURLs.first.url}#$collectionKey";
unawaited(Clipboard.setData(ClipboardData(text: url)));
await shareImageAndUrl(
placeholderBytes,

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import "dart:io";
import 'package:chewie/chewie.dart';
// import 'package:chewie/src/notifiers/player_notifier.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import "package:fluttertoast/fluttertoast.dart";
@@ -22,6 +23,7 @@ import "package:photos/utils/dialog_util.dart";
import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/toast_util.dart';
import "package:photos/utils/wakelock_util.dart";
// import "package:provider/provider.dart";
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
@@ -56,6 +58,8 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
File? previewFile;
bool initializedPlayerNotifier = false;
@override
void initState() {
super.initState();
@@ -237,9 +241,6 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
_videoPlayerController!.addListener(() {
if (_isPlaying != _videoPlayerController!.value.isPlaying) {
_isPlaying = _videoPlayerController!.value.isPlaying;
if (widget.playbackCallback != null) {
widget.playbackCallback!(_isPlaying);
}
unawaited(_keepScreenAliveOnPlaying(_isPlaying));
}
});
@@ -251,10 +252,13 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
looping: true,
allowMuting: true,
allowFullScreen: false,
customControls: VideoControls(
file: widget.file,
onStreamChange: widget.onStreamChange,
),
customControls: (hideStuff) {
widget.playbackCallback!(hideStuff);
return VideoControls(
file: widget.file,
onStreamChange: widget.onStreamChange,
);
},
);
return Container(
color: Colors.black,

View File

@@ -48,7 +48,7 @@ class _EmptyAlbumStateNewState extends State<CollectPhotosBottomButtons> {
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.c.id),
);
final String url = "${widget.c.publicURLs!.first!.url}#$collectionKey";
final String url = "${widget.c.publicURLs.first.url}#$collectionKey";
await shareAlbumLinkWithPlaceholder(
context,
widget.c,

View File

@@ -820,7 +820,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
"Cannot share empty collection of type $galleryType",
);
}
if (Configuration.instance.getUserID() == widget.collection!.owner!.id) {
if (Configuration.instance.getUserID() == widget.collection!.owner.id) {
unawaited(
routeToPage(
context,

View File

@@ -216,7 +216,7 @@ Future<Map<String, Uint8List>?> _getFaceCrops(
faceBoxes,
);
final Map<String, Uint8List> result = {};
for (int i = 0; i < faceIds.length; i++) {
for (int i = 0; i < faceCrop.length; i++) {
result[faceIds[i]] = faceCrop[i];
}
return result;

View File

@@ -170,7 +170,7 @@ Future<List<Uint8List>> generateFaceThumbnailsUsingCanvas(
return faceThumbnails;
} catch (e, s) {
_logger.severe(
'Error generating face thumbnails. cropImage problematic input argument: ${faceBoxes[i]}',
'Error generating face thumbnails. cropImage problematic input argument: ${i}th facebox: ${faceBoxes[i].toString()}',
e,
s,
);

View File

@@ -259,11 +259,11 @@ packages:
dependency: "direct main"
description:
path: "."
ref: forked_video_player_plus
resolved-ref: "2d8908efe9d7533ec76abe2e59444547c4031f28"
url: "https://github.com/ente-io/chewie.git"
ref: latest-changes
resolved-ref: "318339620ff3262b45645aaebc77c5f50a4b6e83"
url: "https://github.com/prateekmedia/chewie.git"
source: git
version: "1.7.1"
version: "1.10.0"
cli_util:
dependency: transitive
description:
@@ -1835,18 +1835,18 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "8.2.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.1.0"
page_transition:
dependency: "direct main"
description:
@@ -2915,18 +2915,18 @@ packages:
dependency: "direct main"
description:
name: wakelock_plus
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e"
url: "https://pub.dev"
source: hosted
version: "1.1.4"
version: "1.2.10"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_plus_platform_interface
sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16"
sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
wallpaper_manager_flutter:
dependency: "direct main"
description:

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.96+996
version: 0.9.98+998
publish_to: none
environment:
@@ -30,8 +30,8 @@ dependencies:
cached_network_image: ^3.0.0
chewie:
git:
url: https://github.com/ente-io/chewie.git
ref: forked_video_player_plus
url: https://github.com/prateekmedia/chewie.git
ref: latest-changes
collection: # dart
computer:
git: "https://github.com/ente-io/computer.git"
@@ -142,7 +142,7 @@ dependencies:
url: https://github.com/ente-io/onnxruntime.git
ref: ios_only
open_mail_app: ^0.4.5
package_info_plus: ^4.1.0
package_info_plus:
page_transition: ^2.0.2
panorama:
git:

View File

@@ -293,6 +293,7 @@ func main() {
BillingCtrl: billingController,
UserRepo: userRepo,
UserCacheCtrl: userCacheCtrl,
UsageRepo: usageRepo,
}
publicCollectionCtrl := &controller.PublicCollectionController{
@@ -621,6 +622,7 @@ func main() {
familiesJwtAuthAPI.GET("/family/members", familyHandler.FetchMembers)
familiesJwtAuthAPI.DELETE("/family/remove-member/:id", familyHandler.RemoveMember)
familiesJwtAuthAPI.DELETE("/family/revoke-invite/:id", familyHandler.RevokeInvite)
familiesJwtAuthAPI.POST("/family/modify-storage", familyHandler.ModifyStorageLimit)
emergencyHandler := &api.EmergencyHandler{
Controller: emergencyCtrl,

View File

@@ -21,26 +21,23 @@ commit to the GitHub Container Registry (GHCR) so that it can be used by folks
without needing to clone our repository just for building an image. For more
details about the use case, see [docker.md](docker.md).
To publish such an external image, firstly find the commit of the currently
running production instance.
These images are published automatically by the "Publish (server)" workflow on
the 15th of every month. If needed, the workflow can also be manually triggered
invoked to publish out of schedule. It can be triggered on the GitHub UI, or by
curl -s https://api.ente.io/ping | jq -r '.id'
```sh
gh workflow run server-publish.yml
```
> We can publish from any arbitrary commit really, but by using the commit
> that's already seen production for a few days, we avoid externally publishing
> images with issues.
Then, trigger the "Publish (server)" workflow, providing it the commit. You can
trigger it either from GitHub's UI or using the `gh cli`. With the CLI, we can
combine both these steps too.
gh workflow run server-publish.yml -F commit=`curl -s https://api.ente.io/ping | jq -r '.id'`
> It uses the commit that is deployed on production museum instances. We can
> publish from any arbitrary commit really, but by using the commit that's
> already seen production, we avoid externally publishing images with issues.
Once the workflow completes, the resultant image will be available at
`ghcr.io/ente-io/server`. The image will be tagged by the commit SHA. The latest
image will also be tagged, well, "latest".
The workflow will also update the branch `server/ghcr` to point to the commit it
used to build the image. This branch will be overwritten on each publish, and
thus it `server/ghcr` points to the code from which the most recent ghcr docker
image for museum has been built.
used to build the image. This branch will be overwritten on each publish; thus
`server/ghcr` will always points to the code from which the most recent ghcr
docker image for museum has been built.

View File

@@ -49,6 +49,11 @@ type FamilyMember struct {
AdminUserID int64 `json:"-"` // for internal use only, ignore from json response
}
type ModifyMemberStorage struct {
ID uuid.UUID `json:"id" binding:"required"`
StorageLimit *int64 `json:"storageLimit"`
}
type FamilyMemberResponse struct {
Members []FamilyMember `json:"members" binding:"required"`
// Family admin subscription storage capacity. This excludes add-on and any other bonus storage

View File

@@ -120,6 +120,21 @@ func (h *FamilyHandler) AcceptInvite(c *gin.Context) {
c.JSON(http.StatusOK, response)
}
// ModifyStorageLimit allows adminUser to Modify the storage for a member in the Family.
func (h *FamilyHandler) ModifyStorageLimit(c *gin.Context) {
var request ente.ModifyMemberStorage
if err := c.ShouldBindJSON(&request); err != nil {
handler.Error(c, stacktrace.Propagate(err, "Could not bind request params"))
return
}
err := h.Controller.ModifyMemberStorage(c, auth.GetUserID(c.Request.Header), request.ID, request.StorageLimit)
if err != nil {
handler.Error(c, stacktrace.Propagate(err, ""))
}
c.JSON(http.StatusOK, nil)
}
// GetInviteInfo returns basic information about invitor/admin as long as the invite is valid
func (h *FamilyHandler) GetInviteInfo(c *gin.Context) {
inviteToken := c.Param("token")

View File

@@ -190,6 +190,56 @@ func (c *Controller) CloseFamily(ctx context.Context, adminID int64) error {
return nil
}
// ModifyMemberStorage allows admin user to update the storageLimit for a member in the family
func (c *Controller) ModifyMemberStorage(ctx context.Context, actorUserID int64, id uuid.UUID, storageLimit *int64) error {
member, err := c.FamilyRepo.GetMemberById(ctx, id)
if err != nil {
return stacktrace.Propagate(err, "Couldn't fetch Family Member")
}
if member.AdminUserID != actorUserID {
return stacktrace.Propagate(ente.ErrPermissionDenied, "you do not have sufficient permission")
}
if member.IsAdmin {
return stacktrace.Propagate(ente.NewBadRequestWithMessage("can not limit admin storage"), "cannot modify admin storage limit")
}
if member.Status != ente.ACCEPTED && member.Status != ente.INVITED {
return stacktrace.Propagate(ente.ErrBadRequest, "user is not a part of family")
}
// gets admin subscription in order to get the size of total storage quota (including bonus)
if storageLimit != nil {
familyMembersData, err := c.FetchMembersForAdminID(ctx, member.AdminUserID)
if err != nil {
return stacktrace.Propagate(ente.ErrBadRequest, "couldn't get active subscription")
}
totalFamilyStorage := familyMembersData.Storage + familyMembersData.AdminBonus
if *storageLimit > totalFamilyStorage {
return stacktrace.Propagate(ente.ErrStorageLimitExceeded, "potential storage limit is more than subscription storage")
}
// Handle if the admin user tries reducing the storage Limit
// and the members Usage is more than the potential storage Limit
memberUsage, memUsageErr := c.UsageRepo.GetUsage(member.MemberUserID)
if memUsageErr != nil {
return stacktrace.Propagate(memUsageErr, "Couldn't find members storage usage")
}
if memberUsage > *storageLimit {
return stacktrace.Propagate(ente.NewBadRequestWithMessage("Failed to reduce storage"), "User's current usage is more")
}
}
modifyStorageErr := c.FamilyRepo.ModifyMemberStorage(ctx, actorUserID, member.ID, storageLimit)
if modifyStorageErr != nil {
return stacktrace.Propagate(modifyStorageErr, "Failed to modify members storage")
}
return nil
}
func (c *Controller) sendNotification(ctx context.Context, adminUserID int64, memberUserID int64, newStatus ente.MemberStatus, inviteToken *string) error {
adminUser, err := c.UserRepo.Get(adminUserID)
if err != nil {

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/ente-io/museum/pkg/controller/usercache"
"github.com/ente-io/museum/pkg/utils/time"
@@ -26,6 +27,7 @@ type Controller struct {
UserRepo *repo.UserRepository
FamilyRepo *repo.FamilyRepository
UserCacheCtrl *usercache.Controller
UsageRepo *repo.UsageRepository
}
// FetchMembers return list of members who are part of a family plan

View File

@@ -196,6 +196,16 @@ func (repo *FamilyRepository) RemoveMember(ctx context.Context, adminID int64, m
return stacktrace.Propagate(tx.Commit(), "failed to commit")
}
// UpdateStorage is used to set Pre-existing Members Storage Limit.
func (repo *FamilyRepository) ModifyMemberStorage(ctx context.Context, adminID int64, id uuid.UUID, storageLimit *int64) error {
_, err := repo.DB.Exec(`UPDATE families SET storage_limit=$1 where id=$2`, storageLimit, id)
if err != nil {
return stacktrace.Propagate(err, "Could not update Members Storage Limit")
}
return stacktrace.Propagate(err, "Failed to Modify Members Storage Limit")
}
// RevokeInvite revokes the invitation invite
func (repo *FamilyRepository) RevokeInvite(ctx context.Context, adminID int64, memberID int64) error {
tx, err := repo.DB.BeginTx(ctx, nil)

View File

@@ -1,17 +1,18 @@
import { staticAppTitle } from "@/base/app";
import { assertionFailed } from "@/base/assert";
import { CustomHead } from "@/base/components/Head";
import { LoadingIndicator } from "@/base/components/loaders";
import { AttributedMiniDialog } from "@/base/components/MiniDialog";
import { useAttributedMiniDialog } from "@/base/components/utils/dialog";
import { useSetupI18n, useSetupLogs } from "@/base/components/utils/hooks-app";
import { photosTheme } from "@/base/components/utils/theme";
import { BaseContext } from "@/base/context";
import "@fontsource-variable/inter";
import { CssBaseline } from "@mui/material";
import { ThemeProvider } from "@mui/material/styles";
import { t } from "i18next";
import type { AppProps } from "next/app";
import React, { useMemo } from "react";
import { AppContext } from "../types/context";
import React, { useCallback, useMemo } from "react";
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
useSetupLogs({ disableDiskLogs: true });
@@ -19,24 +20,32 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const isI18nReady = useSetupI18n();
const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog();
const appContext = useMemo(() => ({ showMiniDialog }), [showMiniDialog]);
const logout = useCallback(() => {
// No code in the accounts app is currently expected to reach a code
// path where they would need to "logout". In any case, the accounts app
// doesn't store any user specific persistent state that'd need to be
// cleared, so there really isn't anything to do here.
assertionFailed();
}, []);
const baseContext = useMemo(
() => ({ logout, showMiniDialog }),
[logout, showMiniDialog],
);
const title = isI18nReady ? t("title_accounts") : staticAppTitle;
return (
<>
<ThemeProvider theme={photosTheme}>
<CustomHead {...{ title }} />
<CssBaseline enableColorScheme />
<AttributedMiniDialog {...miniDialogProps} />
<ThemeProvider theme={photosTheme}>
<CssBaseline enableColorScheme />
<AttributedMiniDialog {...miniDialogProps} />
<AppContext.Provider value={appContext}>
{!isI18nReady && <LoadingIndicator />}
{isI18nReady && <Component {...pageProps} />}
</AppContext.Provider>
</ThemeProvider>
</>
<BaseContext value={baseContext}>
{!isI18nReady && <LoadingIndicator />}
{isI18nReady && <Component {...pageProps} />}
</BaseContext>
</ThemeProvider>
);
};

View File

@@ -10,6 +10,7 @@ import { SingleInputDialog } from "@/base/components/SingleInputDialog";
import { Titlebar } from "@/base/components/Titlebar";
import { errorDialogAttributes } from "@/base/components/utils/dialog";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import log from "@/base/log";
import SingleInputForm from "@ente/shared/components/SingleInputForm";
import { formatDateTimeFull } from "@ente/shared/time/format";
@@ -28,10 +29,9 @@ import {
renamePasskey,
type Passkey,
} from "services/passkey";
import { useAppContext } from "../../types/context";
const Page: React.FC = () => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const [token, setToken] = useState<string | undefined>();
const [passkeys, setPasskeys] = useState<Passkey[]>([]);
@@ -252,7 +252,7 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
passkey,
onUpdateOrDeletePasskey,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const { show: showRenameDialog, props: renameDialogVisibilityProps } =
useModalVisibility();

View File

@@ -1,18 +0,0 @@
import type { AccountsContextT } from "@/accounts/types/context";
import { createContext, useContext } from "react";
/**
* The type of the context for pages in the accounts app.
*/
type AppContextT = Omit<AccountsContextT, "logout">;
/**
* The React {@link Context} available to all nodes in the React tree.
*/
export const AppContext = createContext<AppContextT | undefined>(undefined);
/**
* Utility hook to get the {@link AppContextT} expected to be available to all
* React components in the Accounts app's React tree.
*/
export const useAppContext = (): AppContextT => useContext(AppContext)!;

View File

@@ -2,9 +2,7 @@
"extends": "@/build-config/tsconfig-next.json",
"compilerOptions": {
/* Set the base directory from which to resolve bare module names. */
"baseUrl": "./src",
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
"exactOptionalPropertyTypes": false
"baseUrl": "./src"
},
"include": [
"src",

View File

@@ -1,3 +1 @@
import Page from "@/base/components/pages/404";
export default Page;
export { default } from "@/base/components/pages/404";

View File

@@ -13,13 +13,10 @@ import {
useSetupLogs,
} from "@/base/components/utils/hooks-app";
import { authTheme } from "@/base/components/utils/theme";
import { BaseContext } from "@/base/context";
import { logStartupBanner } from "@/base/log-web";
import HTTPService from "@ente/shared/network/HTTPService";
import {
LS_KEYS,
getData,
migrateKVToken,
} from "@ente/shared/storage/localStorage";
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
import type { User } from "@ente/shared/user/types";
import "@fontsource-variable/inter";
import { CssBaseline } from "@mui/material";
@@ -27,7 +24,6 @@ import { ThemeProvider } from "@mui/material/styles";
import { t } from "i18next";
import type { AppProps } from "next/app";
import React, { useCallback, useEffect, useMemo } from "react";
import { AppContext } from "types/context";
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
useSetupLogs();
@@ -38,7 +34,6 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
useEffect(() => {
const user = getData(LS_KEYS.USER) as User | undefined | null;
void migrateKVToken(user);
logStartupBanner(user?.id);
HTTPService.setHeaders({ "X-Client-Package": clientPackageName });
}, []);
@@ -47,37 +42,30 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
void accountLogout().then(() => window.location.replace("/"));
}, []);
const appContext = useMemo(
() => ({
logout,
showMiniDialog,
}),
const baseContext = useMemo(
() => ({ logout, showMiniDialog }),
[logout, showMiniDialog],
);
const title = isI18nReady ? t("title_auth") : staticAppTitle;
return (
<>
<ThemeProvider theme={authTheme}>
<CustomHead {...{ title }} />
<CssBaseline enableColorScheme />
<AttributedMiniDialog {...miniDialogProps} />
<ThemeProvider theme={authTheme}>
<CssBaseline enableColorScheme />
<AttributedMiniDialog {...miniDialogProps} />
<AppContext.Provider value={appContext}>
{!isI18nReady ? (
<LoadingIndicator />
) : (
<>
{isChangingRoute && <TranslucentLoadingOverlay />}
<Component {...pageProps} />
</>
)}
</AppContext.Provider>
</ThemeProvider>
</>
<BaseContext value={baseContext}>
{!isI18nReady ? (
<LoadingIndicator />
) : (
<>
{isChangingRoute && <TranslucentLoadingOverlay />}
<Component {...pageProps} />
</>
)}
</BaseContext>
</ThemeProvider>
);
};

View File

@@ -8,6 +8,7 @@ import {
OverflowMenu,
OverflowMenuOption,
} from "@/base/components/OverflowMenu";
import { useBaseContext } from "@/base/context";
import { isHTTP401Error } from "@/base/http";
import log from "@/base/log";
import { masterKeyFromSessionIfLoggedIn } from "@/base/session-store";
@@ -28,10 +29,9 @@ import { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react";
import { generateOTPs, type Code } from "services/code";
import { getAuthCodes } from "services/remote";
import { useAppContext } from "types/context";
const Page: React.FC = () => {
const { logout, showMiniDialog } = useAppContext();
const { logout, showMiniDialog } = useBaseContext();
const router = useRouter();
const [codes, setCodes] = useState<Code[]>([]);
@@ -57,7 +57,7 @@ const Page: React.FC = () => {
setHasFetched(true);
};
void fetchCodes();
}, [router, showMiniDialog, logout]);
}, [router, logout, showMiniDialog]);
const lcSearch = searchTerm.toLowerCase();
const filteredCodes = codes.filter(
@@ -131,7 +131,7 @@ const Page: React.FC = () => {
export default Page;
const AuthNavbar: React.FC = () => {
const { logout } = useAppContext();
const { logout } = useBaseContext();
return (
<NavbarBase

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/change-password";
export default Page;
export { default } from "@/accounts/pages/change-password";

View File

@@ -1,6 +1 @@
import Page_ from "@/accounts/pages/credentials";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;
export { default } from "@/accounts/pages/credentials";

View File

@@ -1,6 +1 @@
import Page_ from "@/accounts/pages/generate";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;
export { default } from "@/accounts/pages/generate";

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/login";
export default Page;
export { default } from "@/accounts/pages/login";

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/passkeys/finish";
export default Page;
export { default } from "@/accounts/pages/passkeys/finish";

View File

@@ -1,8 +1,5 @@
import Page_ from "@/accounts/pages/two-factor/recover";
import { useAppContext } from "types/context";
const Page = () => (
<Page_ appContext={useAppContext()} twoFactorType="passkey" />
);
const Page = () => <Page_ twoFactorType="passkey" />;
export default Page;

View File

@@ -1,6 +1 @@
import Page_ from "@/accounts/pages/recover";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;
export { default } from "@/accounts/pages/recover";

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/signup";
export default Page;
export { default } from "@/accounts/pages/signup";

View File

@@ -1,6 +1,5 @@
import Page_ from "@/accounts/pages/two-factor/recover";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} twoFactorType="totp" />;
const Page = () => <Page_ twoFactorType="totp" />;
export default Page;

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/two-factor/setup";
export default Page;
export { default } from "@/accounts/pages/two-factor/setup";

View File

@@ -1,6 +1 @@
import Page_ from "@/accounts/pages/two-factor/verify";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;
export { default } from "@/accounts/pages/two-factor/verify";

View File

@@ -1,6 +1 @@
import Page_ from "@/accounts/pages/verify";
import { useAppContext } from "types/context";
const Page = () => <Page_ appContext={useAppContext()} />;
export default Page;
export { default } from "@/accounts/pages/verify";

View File

@@ -1,13 +0,0 @@
import type { AccountsContextT } from "@/accounts/types/context";
import { createContext, useContext } from "react";
/**
* Properties available via {@link AppContext} to the Auth app's React tree.
*/
type AppContextT = AccountsContextT;
/** The React {@link Context} available to all pages. */
export const AppContext = createContext<AppContextT | undefined>(undefined);
/** Utility hook to reduce amount of boilerplate in account related pages. */
export const useAppContext = () => useContext(AppContext)!;

View File

@@ -2,9 +2,7 @@
"extends": "@/build-config/tsconfig-next.json",
"compilerOptions": {
/* Set the base directory from which to resolve bare module names. */
"baseUrl": "./src",
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
"exactOptionalPropertyTypes": false
"baseUrl": "./src"
},
"include": [
"src",

View File

@@ -10,15 +10,14 @@ import React from "react";
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
useSetupLogs({ disableDiskLogs: true });
return (
<>
<CustomHead title={staticAppTitle} />
// We don't provide BaseContext. Nothing in the cast app needs it yet.
<ThemeProvider theme={castTheme}>
<CssBaseline enableColorScheme />
<Component {...pageProps} />
</ThemeProvider>
</>
return (
<ThemeProvider theme={castTheme}>
<CustomHead title={staticAppTitle} />
<CssBaseline enableColorScheme />
<Component {...pageProps} />
</ThemeProvider>
);
};

View File

@@ -2,9 +2,7 @@
"extends": "@/build-config/tsconfig-next.json",
"compilerOptions": {
/* Set the base directory from which to resolve bare module names. */
"baseUrl": "./src",
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
"exactOptionalPropertyTypes": false
"baseUrl": "./src"
},
"include": [
"src",

View File

@@ -3,7 +3,7 @@ import config from "@/build-config/eslintrc-next-app.mjs";
export default [
...config,
{
ignores: ["thirdparty"],
ignores: ["thirdparty", ".next-desktop"],
},
{
rules: {

View File

@@ -3,15 +3,16 @@ import {
TitledMiniDialog,
type MiniDialogAttributes,
} from "@/base/components/MiniDialog";
import { useBaseContext } from "@/base/context";
import log from "@/base/log";
import { AppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import VerifyMasterPasswordForm, {
type VerifyMasterPasswordFormProps,
} from "@ente/shared/components/VerifyMasterPasswordForm";
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
import type { KeyAttributes, User } from "@ente/shared/user/types";
import { t } from "i18next";
import { useCallback, useContext, useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
interface Iprops {
open: boolean;
@@ -24,7 +25,8 @@ export default function AuthenticateUserModal({
onClose,
onAuthenticate,
}: Iprops) {
const { showMiniDialog, onGenericError, logout } = useContext(AppContext);
const { logout, showMiniDialog } = useBaseContext();
const { onGenericError } = usePhotosAppContext();
const [user, setUser] = useState<User>();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
@@ -47,7 +49,7 @@ export default function AuthenticateUserModal({
// potentially transient issues.
log.warn("Ignoring error when determining session validity", e);
}
}, [showMiniDialog, logout]);
}, [logout, showMiniDialog]);
useEffect(() => {
const main = async () => {

View File

@@ -6,6 +6,11 @@ import {
OverflowMenuOption,
} from "@/base/components/OverflowMenu";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import {
isArchivedCollection,
isPinnedCollection,
} from "@/gallery/services/magic-metadata";
import type { Collection } from "@/media/collection";
import { ItemVisibility } from "@/media/file-metadata";
import {
@@ -22,11 +27,7 @@ import type {
CollectionSummaryType,
} from "@/new/photos/services/collection/ui";
import { clearLocalTrash, emptyTrash } from "@/new/photos/services/collections";
import {
isArchivedCollection,
isPinnedCollection,
} from "@/new/photos/services/magic-metadata";
import { useAppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined";
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
import EditIcon from "@mui/icons-material/Edit";
@@ -128,8 +129,9 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
setFilesDownloadProgressAttributesCreator,
isActiveCollectionDownloadInProgress,
}) => {
const { showLoadingBar, hideLoadingBar, onGenericError, showMiniDialog } =
useAppContext();
const { showMiniDialog } = useBaseContext();
const { showLoadingBar, hideLoadingBar, onGenericError } =
usePhotosAppContext();
const { syncWithRemote } = useContext(GalleryContext);
const overFlowMenuIconRef = useRef<SVGSVGElement>(null);

View File

@@ -15,6 +15,7 @@ import {
} from "@/base/components/RowButton";
import { Titlebar } from "@/base/components/Titlebar";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { sharedCryptoWorker } from "@/base/crypto";
import log from "@/base/log";
import { appendCollectionKeyToShareURL } from "@/gallery/services/share";
@@ -27,7 +28,7 @@ import { COLLECTION_ROLE, type CollectionUser } from "@/media/collection";
import { PublicLinkCreated } from "@/new/photos/components/share/PublicLinkCreated";
import { avatarTextColor } from "@/new/photos/services/avatar";
import type { CollectionSummary } from "@/new/photos/services/collection/ui";
import { AppContext, useAppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import { FlexWrapper } from "@ente/shared/components/Container";
import SingleInputForm, {
type SingleInputFormProps,
@@ -811,7 +812,7 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
onRootClose,
peopleCount,
}) => {
const { showLoadingBar, hideLoadingBar } = useContext(AppContext);
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
const galleryContext = useContext(GalleryContext);
const [addParticipantView, setAddParticipantView] = useState(false);
@@ -1007,7 +1008,7 @@ const ManageParticipant: React.FC<ManageParticipantProps> = ({
selectedParticipant,
collectionUnshare,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const galleryContext = useContext(GalleryContext);
const handleRootClose = () => {
@@ -1710,7 +1711,7 @@ const ManageDownloadAccess: React.FC<ManageDownloadAccessProps> = ({
updatePublicShareURLHelper,
collection,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const handleFileDownloadSetting = () => {
if (publicShareProp.enableDownload) {
@@ -1758,7 +1759,7 @@ const ManageLinkPassword: React.FC<ManageLinkPasswordProps> = ({
publicShareProp,
updatePublicShareURLHelper,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const [changePasswordView, setChangePasswordView] = useState(false);
const closeConfigurePassword = () => setChangePasswordView(false);

View File

@@ -2,12 +2,14 @@ import { assertionFailed } from "@/base/assert";
import { TitledMiniDialog } from "@/base/components/MiniDialog";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { LoadingButton } from "@/base/components/mui/LoadingButton";
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { sharedCryptoWorker } from "@/base/crypto";
import {
DropdownInput,
type DropdownOption,
} from "@/new/photos/components/DropdownInput";
import { AppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import { initiateEmail } from "@/new/photos/utils/web";
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
import { getActualKey } from "@ente/shared/user";
@@ -28,18 +30,17 @@ import { Trans } from "react-i18next";
import { deleteAccount, getAccountDeleteChallenge } from "services/userService";
import * as Yup from "yup";
interface Iprops {
onClose: () => void;
open: boolean;
}
interface FormValues {
reason: string;
feedback: string;
}
const DeleteAccountModal = ({ open, onClose }: Iprops) => {
const { showMiniDialog, onGenericError, logout } = useContext(AppContext);
const DeleteAccountModal: React.FC<ModalVisibilityProps> = ({
open,
onClose,
}) => {
const { logout, showMiniDialog } = useBaseContext();
const { onGenericError } = usePhotosAppContext();
const { authenticateUser } = useContext(GalleryContext);
const [loading, setLoading] = useState(false);

View File

@@ -7,12 +7,12 @@ import {
} from "@/base/components/OverflowMenu";
import { EllipsizedTypography } from "@/base/components/Typography";
import type { ButtonishProps } from "@/base/components/mui";
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { ensureElectron } from "@/base/electron";
import log from "@/base/log";
import { EnteFile } from "@/media/file";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { useAppContext } from "@/new/photos/types/context";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import { CustomError } from "@ente/shared/error";
import FolderIcon from "@mui/icons-material/Folder";
@@ -50,7 +50,7 @@ export const Export: React.FC<ExportProps> = ({
onClose,
collectionNameMap,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const [exportStage, setExportStage] = useState(ExportStage.INIT);
const [exportFolder, setExportFolder] = useState("");
const [continuousExport, setContinuousExport] = useState(false);

View File

@@ -1,6 +1,6 @@
import { useBaseContext } from "@/base/context";
import { Notification } from "@/new/photos/components/Notification";
import { aboveFileViewerContentZ } from "@/new/photos/components/utils/z-index";
import { useAppContext } from "@/new/photos/types/context";
import { t } from "i18next";
import { GalleryContext } from "pages/gallery";
import { useContext } from "react";
@@ -57,7 +57,7 @@ export const FilesDownloadProgress: React.FC<FilesDownloadProgressProps> = ({
attributesList,
setAttributesList,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const galleryContext = useContext(GalleryContext);
if (!attributesList) {

View File

@@ -112,8 +112,8 @@ export interface PhotoFrameProps {
/** This will be set if mode is "people". */
activePersonID?: string | undefined;
enableDownload?: boolean;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
fileToCollectionsMap?: Map<number, number[]>;
collectionNameMap?: Map<number, string>;
showAppDownloadBanner?: boolean;
setIsPhotoSwipeOpen?: (value: boolean) => void;
isInHiddenSection?: boolean;

View File

@@ -9,14 +9,21 @@ import {
} from "@/base/components/utils/modal";
import { lowercaseExtension } from "@/base/file-name";
import log from "@/base/log";
import {
FileInfo,
type FileInfoExif,
type FileInfoProps,
} from "@/gallery/components/FileInfo";
import { downloadManager } from "@/gallery/services/download";
import type { Collection } from "@/media/collection";
import { fileLogID, type EnteFile } from "@/media/file";
import { FileType } from "@/media/file-type";
import { isHEICExtension, needsJPEGConversion } from "@/media/formats";
import { ConfirmDeleteFileDialog } from "@/new/photos/components/FileViewer";
import { ImageEditorOverlay } from "@/new/photos/components/ImageEditorOverlay";
import { moveToTrash } from "@/new/photos/services/collection";
import { extractRawExif, parseExif } from "@/new/photos/services/exif";
import { AppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
@@ -61,6 +68,7 @@ import {
addToFavorites,
removeFromFavorites,
} from "services/collectionService";
import uploadManager from "services/upload/uploadManager";
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
import {
copyFileToClipboard,
@@ -68,8 +76,6 @@ import {
getFileFromURL,
} from "utils/file";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
import { FileInfo, type FileInfoExif, type FileInfoProps } from "./FileInfo";
import { ImageEditorOverlay } from "./ImageEditorOverlay";
export type PhotoViewerProps = Pick<
PhotoFrameProps,
@@ -99,8 +105,8 @@ export type PhotoViewerProps = Pick<
isInHiddenSection: boolean;
enableDownload: boolean;
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
fileToCollectionsMap: Map<number, number[]>;
collectionNameMap: Map<number, string>;
fileToCollectionsMap?: Map<number, number[]>;
collectionNameMap?: Map<number, string>;
onSelectPerson?: FileInfoProps["onSelectPerson"];
};
@@ -137,8 +143,8 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
collectionNameMap,
onSelectPerson,
}) => {
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
const galleryContext = useContext(GalleryContext);
const { showLoadingBar, hideLoadingBar } = useContext(AppContext);
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext,
);
@@ -650,6 +656,18 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
setShowImageEditorOverlay(false);
};
const handleSaveEditedCopy = (
editedFile: File,
collection: Collection,
enteFile: EnteFile,
) => {
uploadManager.prepareForNewUpload();
uploadManager.showUploadProgressDialog();
uploadManager.uploadFile(editedFile, collection, enteFile);
handleCloseEditor();
handleClose();
};
const downloadFileHelper = async (file: EnteFile) => {
if (
file &&
@@ -717,6 +735,18 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
}
};
const handleSelectCollection = (collectionID: number) => {
galleryContext.onShowCollection(collectionID);
handleClose();
};
const handleSelectPerson = onSelectPerson
? (personID: string) => {
onSelectPerson(personID);
handleClose();
}
: undefined;
const handleForceConvert = () =>
forceConvertItem(
photoSwipe,
@@ -964,10 +994,10 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
<FileInfo
showInfo={showInfo}
handleCloseInfo={handleCloseInfo}
closePhotoViewer={handleClose}
file={photoSwipe?.currItem as EnteFile}
exif={exif?.value}
shouldDisableEdits={!isOwnFile}
allowMap={!publicCollectionGalleryContext.credentials}
showCollectionChips={
!isTrashCollection && isOwnFile && !isInHiddenSection
}
@@ -975,13 +1005,14 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
refreshPhotoswipe={refreshPhotoswipe}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}
onSelectPerson={onSelectPerson}
onSelectCollection={handleSelectCollection}
onSelectPerson={handleSelectPerson}
/>
<ImageEditorOverlay
show={showImageEditorOverlay}
file={photoSwipe?.currItem as EnteFile}
open={showImageEditorOverlay}
onClose={handleCloseEditor}
closePhotoViewer={handleClose}
file={photoSwipe?.currItem as EnteFile}
onSaveEditedCopy={handleSaveEditedCopy}
/>
</>
);

View File

@@ -12,6 +12,7 @@ import {
} from "@/base/components/RowButton";
import { SpacedRow } from "@/base/components/containers";
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
import {
NestedSidebarDrawer,
SidebarDrawer,
@@ -20,6 +21,7 @@ import {
} from "@/base/components/mui/SidebarDrawer";
import { useIsSmallWidth } from "@/base/components/utils/hooks";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { isDevBuild } from "@/base/env";
import {
getLocaleInUse,
@@ -33,7 +35,6 @@ import { savedLogs } from "@/base/log-web";
import { customAPIHost } from "@/base/origins";
import { downloadString } from "@/base/utils/web";
import { DropdownInput } from "@/new/photos/components/DropdownInput";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { MLSettings } from "@/new/photos/components/sidebar/MLSettings";
import { TwoFactorSettings } from "@/new/photos/components/sidebar/TwoFactorSettings";
import {
@@ -75,7 +76,7 @@ import {
userDetailsAddOnBonuses,
type UserDetails,
} from "@/new/photos/services/user-details";
import { AppContext, useAppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import { initiateEmail, openURL } from "@/new/photos/utils/web";
import {
FlexWrapper,
@@ -354,7 +355,7 @@ const SubscriptionStatus: React.FC<SubscriptionStatusProps> = ({
};
function MemberSubscriptionManage({ open, userDetails, onClose }) {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const fullScreen = useIsSmallWidth();
const confirmLeaveFamily = () =>
@@ -508,7 +509,7 @@ const UtilitySection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
closeSidebar,
}) => {
const router = useRouter();
const { watchFolderView, setWatchFolderView } = useAppContext();
const { watchFolderView, setWatchFolderView } = usePhotosAppContext();
const { show: showAccount, props: accountVisibilityProps } =
useModalVisibility();
@@ -562,7 +563,7 @@ const UtilitySection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
const HelpSection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
closeSidebar,
}) => {
const { showMiniDialog } = useContext(AppContext);
const { showMiniDialog } = useBaseContext();
const { openExportModal } = useContext(GalleryContext);
const { show: showHelp, props: helpVisibilityProps } = useModalVisibility();
@@ -595,7 +596,7 @@ const HelpSection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
};
const ExitSection: React.FC = () => {
const { showMiniDialog, logout } = useContext(AppContext);
const { logout, showMiniDialog } = useBaseContext();
const handleLogout = () =>
showMiniDialog({
@@ -648,7 +649,7 @@ const Account: React.FC<NestedSidebarDrawerVisibilityProps> = ({
onClose,
onRootClose,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const router = useRouter();
@@ -908,7 +909,7 @@ const MapSettings: React.FC<NestedSidebarDrawerVisibilityProps> = ({
onClose,
onRootClose,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const { mapEnabled } = useSettingsSnapshot();
@@ -1033,7 +1034,7 @@ const Help: React.FC<NestedSidebarDrawerVisibilityProps> = ({
onClose,
onRootClose,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const handleRootClose = () => {
onClose();

View File

@@ -1,9 +1,6 @@
import { FilledIconButton } from "@/base/components/mui";
import {
UPLOAD_RESULT,
type UploadPhase,
} from "@/new/photos/services/upload/types";
import { useAppContext } from "@/new/photos/types/context";
import { useBaseContext } from "@/base/context";
import { UPLOAD_RESULT, type UploadPhase } from "@/gallery/services/upload";
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
import CloseIcon from "@mui/icons-material/Close";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
@@ -67,7 +64,8 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
finishedUploads,
cancelUploads,
}) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const [expanded, setExpanded] = useState(false);
useEffect(() => {

View File

@@ -1,8 +1,8 @@
import { SpacedRow } from "@/base/components/containers";
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import { RowButton } from "@/base/components/RowButton";
import { useIsTouchscreen } from "@/base/components/utils/hooks";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import GoogleIcon from "@mui/icons-material/Google";
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";

View File

@@ -1,8 +1,14 @@
import { isDesktop } from "@/base/app";
import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { basename } from "@/base/file-name";
import log from "@/base/log";
import type { CollectionMapping, Electron, ZipItem } from "@/base/types/ipc";
import type {
FileAndPath,
UploadItem,
UploadPhase,
} from "@/gallery/services/upload";
import type { Collection } from "@/media/collection";
import type { EnteFile } from "@/media/file";
import { UploaderNameInput } from "@/new/albums/components/UploaderNameInput";
@@ -11,13 +17,8 @@ import type { CollectionSelectorAttributes } from "@/new/photos/components/Colle
import { downloadAppDialogAttributes } from "@/new/photos/components/utils/download";
import { getLatestCollections } from "@/new/photos/services/collections";
import { exportMetadataDirectoryName } from "@/new/photos/services/export";
import type {
FileAndPath,
UploadItem,
UploadPhase,
} from "@/new/photos/services/upload/types";
import { redirectToCustomerPortal } from "@/new/photos/services/user-details";
import { useAppContext } from "@/new/photos/types/context";
import { usePhotosAppContext } from "@/new/photos/types/context";
import { firstNonEmpty } from "@/utils/array";
import { CustomError } from "@ente/shared/error";
import DiscFullIcon from "@mui/icons-material/DiscFull";
@@ -108,12 +109,9 @@ export default function Uploader({
showSessionExpiredMessage,
...props
}: Props) {
const {
showMiniDialog,
showNotification,
onGenericError,
watchFolderView,
} = useAppContext();
const { showMiniDialog } = useBaseContext();
const { showNotification, onGenericError, watchFolderView } =
usePhotosAppContext();
const galleryContext = useContext(GalleryContext);
const publicCollectionGalleryContext = useContext(
PublicCollectionGalleryContext,

View File

@@ -1,4 +1,5 @@
import { CenteredFill, SpacedRow } from "@/base/components/containers";
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
import {
OverflowMenu,
@@ -9,12 +10,11 @@ import {
useModalVisibility,
type ModalVisibilityProps,
} from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import { ensureElectron } from "@/base/electron";
import { basename, dirname } from "@/base/file-name";
import type { CollectionMapping, FolderWatch } from "@/base/types/ipc";
import { CollectionMappingChoice } from "@/new/photos/components/CollectionMappingChoice";
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
import { useAppContext } from "@/new/photos/types/context";
import AddIcon from "@mui/icons-material/Add";
import CheckIcon from "@mui/icons-material/Check";
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
@@ -205,7 +205,7 @@ interface WatchEntryProps {
}
const WatchEntry: React.FC<WatchEntryProps> = ({ watch, removeWatch }) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const confirmStopWatching = () => {
showMiniDialog({

View File

@@ -1,4 +1,5 @@
import { SpacedRow } from "@/base/components/containers";
import { useBaseContext } from "@/base/context";
import type { Collection } from "@/media/collection";
import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector";
import type { GalleryBarMode } from "@/new/photos/components/gallery/reducer";
@@ -7,7 +8,6 @@ import {
ARCHIVE_SECTION,
TRASH_SECTION,
} from "@/new/photos/services/collection";
import { useAppContext } from "@/new/photos/types/context";
import ClockIcon from "@mui/icons-material/AccessTime";
import AddIcon from "@mui/icons-material/Add";
import ArchiveIcon from "@mui/icons-material/ArchiveOutlined";
@@ -71,7 +71,7 @@ const SelectedFileOptions = ({
isInSearchMode,
isInHiddenSection,
}: Props) => {
const { showMiniDialog } = useAppContext();
const { showMiniDialog } = useBaseContext();
const peopleMode = barMode == "people";

View File

@@ -1,3 +1 @@
import Page from "@/base/components/pages/404";
export default Page;
export { default } from "@/base/components/pages/404";

View File

@@ -16,6 +16,7 @@ import {
useSetupLogs,
} from "@/base/components/utils/hooks-app";
import { photosTheme } from "@/base/components/utils/theme";
import { BaseContext } from "@/base/context";
import log from "@/base/log";
import { logStartupBanner } from "@/base/log-web";
import { AppUpdate } from "@/base/types/ipc";
@@ -30,12 +31,12 @@ import { aboveFileViewerContentZ } from "@/new/photos/components/utils/z-index";
import { runMigrations } from "@/new/photos/services/migration";
import { initML, isMLSupported } from "@/new/photos/services/ml";
import { getFamilyPortalRedirectURL } from "@/new/photos/services/user-details";
import { AppContext } from "@/new/photos/types/context";
import { PhotosAppContext } from "@/new/photos/types/context";
import HTTPService from "@ente/shared/network/HTTPService";
import {
getData,
isLocalStorageAndIndexedDBMismatch,
LS_KEYS,
migrateKVToken,
} from "@ente/shared/storage/localStorage";
import type { User } from "@ente/shared/user/types";
import "@fontsource-variable/inter";
@@ -68,13 +69,21 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const [watchFolderView, setWatchFolderView] = useState(false);
const logout = useCallback(() => void photosLogout(), []);
useEffect(() => {
const user = getData(LS_KEYS.USER) as User | undefined | null;
void migrateKVToken(user);
logStartupBanner(user?.id);
HTTPService.setHeaders({ "X-Client-Package": clientPackageName });
void runMigrations();
}, []);
void isLocalStorageAndIndexedDBMismatch().then((mismatch) => {
if (mismatch) {
log.error("Logging out (IndexedDB and local storage mismatch)");
return logout();
} else {
return runMigrations();
}
});
}, [logout]);
useEffect(() => {
const electron = globalThis.electron;
@@ -149,8 +158,10 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
}, 0);
}, []);
const logout = useCallback(() => void photosLogout(), []);
const baseContext = useMemo(
() => ({ logout, showMiniDialog }),
[logout, showMiniDialog],
);
const appContext = useMemo(
() => ({
showLoadingBar,
@@ -176,22 +187,21 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
const title = isI18nReady ? t("title_photos") : staticAppTitle;
return (
<>
<ThemeProvider theme={photosTheme}>
<CustomHead {...{ title }} />
<CssBaseline enableColorScheme />
<ThemedLoadingBar ref={loadingBarRef} />
<ThemeProvider theme={photosTheme}>
<CssBaseline enableColorScheme />
<ThemedLoadingBar ref={loadingBarRef} />
<AttributedMiniDialog
sx={{ zIndex: aboveFileViewerContentZ }}
{...miniDialogProps}
/>
<AttributedMiniDialog
sx={{ zIndex: aboveFileViewerContentZ }}
{...miniDialogProps}
/>
<Notification {...notificationProps} />
<Notification {...notificationProps} />
{isDesktop && <WindowTitlebar>{title}</WindowTitlebar>}
<AppContext.Provider value={appContext}>
{isDesktop && <WindowTitlebar>{title}</WindowTitlebar>}
<BaseContext value={baseContext}>
<PhotosAppContext value={appContext}>
{!isI18nReady ? (
<LoadingIndicator />
) : (
@@ -200,9 +210,9 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
<Component {...pageProps} />
</>
)}
</AppContext.Provider>
</ThemeProvider>
</>
</PhotosAppContext>
</BaseContext>
</ThemeProvider>
);
};

View File

@@ -1,3 +1 @@
import Page from "@/accounts/pages/change-email";
export default Page;
export { default } from "@/accounts/pages/change-email";

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