Compare commits

..

282 Commits

Author SHA1 Message Date
Neeraj Gupta
704b28815b Update flutter submodule to 3.32.8 2025-09-08 09:32:07 +05:30
Neeraj Gupta
8d55eb70fe Merge branch 'main' into f-droid 2025-09-06 07:40:53 +05:30
Neeraj
49c90a802a [mob] Fix changelog scrolling on small devices (#7059)
## Description

## Tests
2025-09-04 12:02:11 +05:30
Neeraj Gupta
8b2db5e576 [mob] Fix changelog scrolling on small devices 2025-09-04 12:00:11 +05:30
Neeraj
b5d4839e04 [mob] Update change log and bump version (#7052)
## Description

## Tests
2025-09-03 15:22:36 +05:30
Neeraj Gupta
ac57097eb4 Update change log and bump version 2025-09-03 15:21:12 +05:30
Ashil
4e08e38bf6 [mob][photos] Update claude md documentation (#7051)
## Description

See commit messages.
2025-09-03 13:29:55 +05:30
ashilkn
a7d3cf4178 Update storage dependencies to reflect current usage
Replace sqflite with sqlite_async as the primary database package since the project has migrated to using sqlite_async.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-03 13:21:35 +05:30
ashilkn
c63dfc36e9 Remove integration and performance test sections from CLAUDE.md
These test commands are not confirmed to be working correctly and have been removed from the documentation.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-03 13:20:12 +05:30
Manav Rathi
2985503254 Update CONTRIBUTING.md (#7050) 2025-09-03 12:54:24 +05:30
Laurens Priem
9be023d68a [mob][photos] Add claude.md (#7044)
## Description

Add claude.md
2025-09-03 12:35:53 +05:30
laurenspriem
6a6e1b3c47 Individual preferences 2025-09-03 12:03:51 +05:30
Neeraj
7516363715 [mob][photos] Prevent vectorDB index file corruption (#7049)
## Description

- Use `load` instead of `view`, since latter is read-only
- When loading fails in rust, delete index file in dart side and try
again
- Atomically save index file by first writing to temp file

## Tests

Tested in debug mode on my pixel phone.
2025-09-03 11:54:31 +05:30
laurenspriem
2b76b71db8 atomic save of index file 2025-09-03 11:15:07 +05:30
Manav Rathi
c32a70fb25 Update CONTRIBUTING.md 2025-09-03 10:52:03 +05:30
laurenspriem
4098c1a072 Delete index file on load error 2025-09-03 10:36:03 +05:30
laurenspriem
972be1f41e Use load for usearch index 2025-09-03 10:27:30 +05:30
laurenspriem
3acb2136d0 [mob][photos] Add documentation sync requirement to CLAUDE.md
Require updating associated spec documents when code changes are made

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 18:21:57 +05:30
laurenspriem
eba729625f commit instructions 2025-09-02 18:19:34 +05:30
Manav Rathi
a477742cd0 [web] Fix European date format search support (#7043)
Fixes #7025
2025-09-02 17:48:10 +05:30
laurenspriem
c974bde11c Don't go to setup on error 2025-09-02 17:02:51 +05:30
Manav Rathi
ecc654bae0 [web] Fix European date format search support
Fixes #7025

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 10:56:53 +00:00
Ashil
201ef88305 [mob][debug] Thumbnail issue debug (#7042)
## Description

For figuring out root cause of thumbnail not loading issue. This change
will not introduce any regressions or bugs.
2025-09-02 16:26:46 +05:30
Ashil
742035d7cc Merge branch 'main' into thunmbail_issue_debug 2025-09-02 16:20:43 +05:30
ashilkn
8f29d5aa19 Update internal change log 2025-09-02 16:18:15 +05:30
laurenspriem
8a4e76fb6f Small rectification 2025-09-02 16:17:10 +05:30
ashilkn
c03eaf83aa Complete completer with error if getThumbnailFromLocal throws error for task in local thumbnail task queue 2025-09-02 16:13:49 +05:30
laurenspriem
378878538d [mob][photos] Add critical coding requirements to CLAUDE.md
Add three mandatory development practices:
1. Run flutter analyze after every change - zero issues required
2. Always reuse existing components - search before creating
3. Use Ente design system - no hardcoded colors or text styles

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 16:06:42 +05:30
laurenspriem
01c3d6b105 [mob][photos] Add CLAUDE.md with initial project documentation
Create comprehensive development guide from /init command including:
- Project philosophy and privacy focus
- Monorepo context and structure
- Development commands (melos and flutter)
- Architecture overview with service patterns
- Security architecture details
- Development setup requirements

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 15:48:43 +05:30
Neeraj
c6f5c68f1e [mob] Update copy (#7040)
## Description

## Tests
2025-09-02 13:52:53 +05:30
Neeraj Gupta
d0c8925ff3 Update playstore changelog 2025-09-02 13:42:46 +05:30
Neeraj Gupta
d6c84421ce [mob][photos] Update changelog copy translations
Updated cLTitle2 from "Manual video stream generation" to "Video streaming enhancements" across all supported locales to match the updated English copy.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 13:41:46 +05:30
Neeraj
0d1f20f9e2 [mob][photos] Clear up flutter analyze (#7035)
## Description

- Replace withOpacity() with withValues(alpha:)
- Replace onPopInvoked with onPopInvokedWithResult
- Update MaterialState references to WidgetState
- Organize imports
- Remove unneeded nullability
- Dangling library docs
- collectionName deprecation warning
- TextInputWidget isPasswordInput deprecation warning
2025-09-02 12:29:26 +05:30
Ashil
c55447a08f [mob][debug] To debug thumbnail not loading (#7036) 2025-09-02 12:28:34 +05:30
Ashil
98d56e8fa4 Merge branch 'main' into thunmbail_issue_debug 2025-09-02 12:26:48 +05:30
ashilkn
f244c94ebf Update internal change log 2025-09-02 12:24:01 +05:30
laurenspriem
88f2b88f4d Remove deprecation warnings 2025-09-02 12:15:07 +05:30
Neeraj
db1fef40db [mob] Update changelog (#7034)
## Description

## Tests
2025-09-02 12:14:36 +05:30
laurenspriem
1fd29cdd13 dangling library doc 2025-09-02 12:02:53 +05:30
laurenspriem
947d294afe non nullable dialog 2025-09-02 12:02:36 +05:30
ashilkn
515715660e Add option to config local thumbnail queue to debug thumbnail not displaying issue + add more logging + show local ID of file on thumbnails (configurable) 2025-09-02 11:53:21 +05:30
laurenspriem
324221171d organize imports 2025-09-02 11:50:01 +05:30
laurenspriem
f5f2ff1b2c Fix Flutter deprecation warnings
- Replace withOpacity() with withValues(alpha:)
- Replace onPopInvoked with onPopInvokedWithResult
- Update MaterialState references to WidgetState

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 11:49:12 +05:30
Neeraj Gupta
244d41621c Bump version 2025-09-02 11:41:40 +05:30
Neeraj Gupta
91b6a08a35 Update changelog entries with new features
- Replace old changelog entries with new ones across all supported languages
- Add Similar Images, Manual video stream generation, and Performance Improvements features
- Remove outdated entries for Advanced Image Editor, Smart Albums, Improved Gallery, and Faster Scroll

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 11:39:54 +05:30
Neeraj
770a311da5 [auth] Fix manual app lock with macos touch id (#6793)
## Description
This fixes https://github.com/ente-io/ente/issues/3428
This was broken because of
https://github.com/eaceto/flutter_local_authentication/issues/8
I've also added that if the app is locked manually, the macOS Touch ID
API won't be called until the user either presses the unlock button
again or unfocuses the app and then focuses back on it. This behavior
also applies when the app window is closed and then reopened.
2025-09-02 10:46:56 +05:30
Neeraj
db76dee639 fix: only show when video streaming is enabled (#7031)
## Description

## Tests
2025-09-02 10:45:37 +05:30
Manav Rathi
20ce760e85 feat(rust): Initialize Rust CLI foundation (#6915)
## Summary
Rust CLI achieves feature parity with Go CLI for photos app core
functionality

## Changes
- Export, sync, and incremental updates working
- Hash-based deduplication and live photo support
- Public magic metadata for renamed files
- Progress indicators for downloads

## Remaining
- Export filters (album, date range)
- Resume interrupted downloads
- Shared/hidden album support
2025-09-02 09:57:56 +05:30
Prateek Sunal
df1bfbe839 fix: initialize compute controllers async with values 2025-09-02 02:51:09 +05:30
Prateek Sunal
27d72eb821 fix: make continuation and releasing compute better 2025-09-02 02:44:11 +05:30
Prateek Sunal
98786c5824 fix: move logs at better place 2025-09-02 01:04:24 +05:30
Prateek Sunal
d38a09c3f0 perf: optimize video stream processing state management
- Move isCurrentlyProcessing to widget state for better performance
- Only call setState when processing state actually changes
- Add comprehensive processing status handling (retry, compressing, uploading)
- Remove redundant service calls from build method
- Clean up unnecessary early returns and duplicate logic

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 18:50:00 +00:00
Aman Raj Singh Mourya
91785d8c90 Add Parallels custom icon (#7026)
## Description
This PR adds a custom icon for Parallels.

- Added `parallels.svg` under
`mobile/apps/auth/assets/custom-icons/icons/`
- Updated `custom-icons.json` with:
  - title: "Parallels"
  - slug: "parallels"
  - hex: #E61E25
  - altNames: ["Parallels Desktop", "Parallels VM"]

The icon is optimized (well under 50KB) and uses the official Parallels
red (#E61E25).
2025-09-02 00:19:46 +05:30
Prateek Sunal
b1f28e3f2e chore: update locks 2025-09-01 23:35:43 +05:30
Prateek Sunal
c155bdd058 chore: lint fixes 2025-09-01 23:35:30 +05:30
Prateek Sunal
a859f28e2c fix: show queueed or creatingStream based on context 2025-09-01 23:35:25 +05:30
Prateek Sunal
8d75528aa5 fix: introduce in queue and creating stream two types of statuses 2025-09-01 23:35:08 +05:30
Prateek Sunal
7f43c11985 fix: only show when video streaming is enabled 2025-09-01 21:22:17 +05:30
Manav Rathi
aadda7e3f6 feat(export): Add file deletion and rename detection to match Go CLI
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 18:01:01 +05:30
Ashil
210c18d244 Update internal changes (#7030) 2025-09-01 17:32:12 +05:30
Ashil
6636849838 update internal changes 2025-09-01 17:26:20 +05:30
Neeraj
5500315351 [mob][photos] fix unsupported locales in language selector (#7029) 2025-09-01 17:24:32 +05:30
Prateek Sunal
562292e642 fix: remove unsupported languages from language picker
Remove languages from _getLanguageName that don't have >90% translation
coverage and aren't in appSupportedLocales (Finnish, Korean, Arabic).
Also improve Chinese locale display.

- Removed fi, ko, ar cases that don't meet translation threshold
- Fixed Chinese locale handling to properly show "中文 (简体)" for zh_CN
- Ensures only properly translated languages appear in the picker

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-01 17:13:58 +05:30
Prateek Sunal
4aa80edbcf fix: resolve unsupported locales appearing in language selector
Replace AppLocalizations.supportedLocales with a curated list of properly
translated locales in the Photos app. This fixes the issue where unsupported
language codes (Bg, Be, Ca, Cs, etc.) were appearing in the language selector
without proper language name formatting.

- Add custom appSupportedLocales list with only >90% translated languages
- Update all references throughout Photos app to use the custom locale list
- Ensures only properly supported languages appear in the language picker

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-01 17:02:15 +05:30
Neeraj
9524a639cd [server] Fix collection link for locker (#6961)
## Description

## Tests
2025-09-01 16:28:19 +05:30
Neeraj
b8eb793c16 [mobile/photos] New translations (#7022)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-app)
2025-09-01 16:26:51 +05:30
Ashil
4b514f1e1a [mob][photos] Revert "Revert diskLoadDeferDuration to 500ms" (#7028)
This reverts commit a295f223b6.
2025-09-01 16:25:59 +05:30
eYdr1en
bee2bb9621 remove unusuded variable 2025-09-01 12:45:27 +02:00
ashilkn
772121c22e Revert "Revert diskLoadDeferDuration to 500ms"
This reverts commit a295f223b6.
2025-09-01 16:07:40 +05:30
eYdr1en
3c49ca0f6e Merge branch 'main' into touch-id 2025-09-01 12:35:27 +02:00
Onurcan
f2e51893ad Update Parallels custom-icons.json 2025-09-01 11:59:32 +03:00
Onurcan
c08b78c775 dd Parallels custom icon 2025-09-01 11:58:26 +03:00
Manav Rathi
233f03355f Fix security issues and match Go CLI error handling
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-01 13:16:03 +05:30
Laurens Priem
73ab50f113 [mob][photos] Run vectorDB migration is memory safe way (#7024)
## Description

- Add ability to block computeController temporarily
- Block computeController when vectorDB migration is running
2025-09-01 11:44:39 +05:30
laurenspriem
4a2346fe93 Block compute when vectorDB migration is happening 2025-09-01 11:11:10 +05:30
laurenspriem
68b5cce158 Add option to block compute tasks (ml, streaming) 2025-09-01 11:09:51 +05:30
laurenspriem
e907a9e8cb comment 2025-09-01 10:59:02 +05:30
Manav Rathi
92a40afca2 [web] New translations (#7021)
New translations from
[Crowdin](https://crowdin.com/project/ente-photos-web)
2025-09-01 10:22:50 +05:30
Aman Raj Singh Mourya
0c2b38c059 Fixing dev build on macos (#7012)
This should fix this error
https://github.com/ente-io/ente/pull/6768#discussion_r2310164866
2025-09-01 09:26:51 +05:30
Crowdin Bot
19650bcd57 New Crowdin translations by GitHub Action 2025-09-01 01:05:33 +00:00
Crowdin Bot
2b9ca073ce New Crowdin translations by GitHub Action 2025-09-01 00:45:39 +00:00
Manav Rathi
2257087bb2 Fix file rename handling to match Go CLI behavior
- Add rename detection by tracking files via ID in metadata
- Remove old files (including live photo MOV components) when renamed
- Copy live photo MOV components during hash deduplication
- Preserve file deduplication optimization while handling renames correctly

This ensures that when a file is renamed in Ente, the old file is removed
and replaced with the renamed version, matching the Go CLI's behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 21:56:14 +05:30
Manav Rathi
2a5bce2ae4 Fix live photo export to preserve original file extensions
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 10:36:25 +05:30
Manav Rathi
1e0a6eb1ea Add persistent storage for public magic metadata
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 09:28:00 +05:30
Manav Rathi
187a729013 Update CLAUDE.md documentation to reflect current codebase
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 09:10:50 +05:30
Manav Rathi
c98f4dfffd fix(rust): Match Go CLI email filtering behavior
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 07:39:59 +05:30
Manav Rathi
4140a0f6fe feat(rust): Add shared album decryption support
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 06:54:33 +05:30
Aman Raj Singh Mourya
cf4b87dad9 [locker] Refactor theme handling in Locker to fix DynamicFAB style (#7016)
## Description
DynamicFAB theme was not getting applied from the commons package. This
PR fix that issue.

## Tests

**Before**

<img width="300" height="750" alt="Simulator Screenshot - iPhone 16 Plus
- 2025-08-30 at 15 53 26"
src="https://github.com/user-attachments/assets/17dfc778-b652-4e10-ad8f-3c8aed2656f6"
/>


**After**

<img width="300" height="750" alt="Simulator Screenshot - iPhone 16 Plus
- 2025-08-30 at 15 52 58"
src="https://github.com/user-attachments/assets/9e0c2feb-8204-4875-9bad-f9d4eaab8f36"
/>
2025-08-30 16:01:01 +05:30
AmanRajSinghMourya
3fd0db6a90 Refactor theme handling in locker to fix DynamicFAB 2025-08-30 15:52:34 +05:30
Manav Rathi
ac68b99ecf Fix shared collection deserialization
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-30 10:53:44 +05:30
Manav Rathi
82e1a0e358 Fix hidden album filtering to match Go CLI
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-30 10:37:45 +05:30
Laurens Priem
ce1701d211 [mob][photos] Similar small fixes (#7008)
## Description

Small design changes and fixes.

## Tests

Tested in debug mode on my pixel phone.
2025-08-30 07:29:27 +05:30
Manav Rathi
034e789242 fix(rust): Validate account exists before update/delete
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 22:02:35 +05:30
Manav Rathi
ccfec4071f fix(rust): Match Go CLI JSON field naming for ID fields
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 21:51:34 +05:30
Manav Rathi
c4830732fd fix(rust): Format timestamps as ISO 8601 in metadata JSON
Changed metadata export to match Go CLI's timestamp format.
Timestamps now serialize as ISO 8601 strings with timezone offset
(e.g., "2025-07-23T19:48:06.098+05:30") instead of Unix microseconds.

Also fixed clippy warnings to ensure CI compliance.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 21:41:33 +05:30
Manav Rathi
72dc56e41f fix(rust): Correct album-based export to match Go CLI
Fixed export to properly organize files into album folders by:
- Fetching files from all collections using /collections/v2/diff endpoint
- Decrypting encrypted collection names to get actual album names
- Using decrypted album names for folder organization

Files now export to proper album folders instead of all going to
"Uncategorized". Tested and verified with local data.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 21:20:30 +05:30
Laurens Priem
aaed336991 [infra] Release action changes (#7010)
## Description

- Disk space cleanup
- Generate rust bindings
2025-08-29 20:05:31 +05:30
eYdr1en
0b85dfe7e4 fixing dev build on macos 2025-08-29 15:53:56 +02:00
Prateek Sunal
68422b172f [mob][photos] fix manual video streaming when ML is enabled (#7009) 2025-08-29 18:53:53 +05:30
laurenspriem
db99dae3e1 log line 2025-08-29 18:48:00 +05:30
laurenspriem
3717a156d3 Logging unexpected embeddings 2025-08-29 18:47:01 +05:30
Prateek Sunal
ca9930e01b style: fix import directive ordering in thumbnail_widget 2025-08-29 12:50:13 +00:00
laurenspriem
eb23a4e770 rust bindings 2025-08-29 18:19:50 +05:30
laurenspriem
e03303e5b3 release workflow disk cleanup 2025-08-29 18:18:45 +05:30
laurenspriem
2ad27f1c6e Clear similar images json cache 2025-08-29 18:03:27 +05:30
Prateek Sunal
202e6a9f7c fix: trigger processing for already-queued manual stream files
When users click "Create Stream" on files already in queue from
previous sessions, ensure processing actually starts even if the
file was previously stalled due to ML blocking.

Add forceProcess parameter to queueFiles() to bypass the existing
queue check and trigger processing of stalled manual queue items.
2025-08-29 12:28:48 +00:00
laurenspriem
ceaedad327 debug option to delete vectorDB index 2025-08-29 17:57:21 +05:30
Prateek Sunal
fd963a1c8e fix: allow manual video stream creation when ML is waiting
When ML is enabled but not running, the compute controller blocks
all stream requests due to _waitingToRunML flag. This prevents
users from manually creating video streams even though ML isn't
actively using resources.

Add bypassMLWaiting parameter to allow manual stream creation
to proceed regardless of ML waiting state, improving UX.
2025-08-29 12:22:08 +00:00
laurenspriem
b40b5bb1ae delete progress 2025-08-29 17:07:44 +05:30
laurenspriem
91827626b2 dot dot dot 2025-08-29 17:04:33 +05:30
laurenspriem
42318335ae Fix issue with deleting favorites 2025-08-29 16:16:51 +05:30
laurenspriem
858db62385 Left align large files 2025-08-29 16:09:10 +05:30
laurenspriem
46e36612d3 Scroll to top after delete 2025-08-29 15:49:09 +05:30
laurenspriem
62cf236e3b Cycle through loading screen texts 2025-08-29 15:27:11 +05:30
Ashil
c2b1ab86f2 [mob][photos] Fix incorrect file deletion from db when widget unmounts during thumbnail loading (#7007)
## Description

Previously, when _loadWithRetry returned null due to widget unmounting,
the code incorrectly assumed the local file was deleted and would remove
database reference of the file and which would trigger re-upload of the
file.
2025-08-29 15:17:55 +05:30
laurenspriem
43adf42281 Don't auto select favorites for deletions 2025-08-29 15:00:42 +05:30
laurenspriem
1e2a65281c Fix delete button bug 2025-08-29 14:32:12 +05:30
ashilkn
70eb68b13c Fix incorrect file deletion when widget unmounts during thumbnail loading
Previously, when _loadWithRetry returned null due to widget unmounting,
the code incorrectly assumed the local file was deleted and would remove
database references or delete the file. This could lead to data loss.

Changes:
- Add new WidgetUnmountedException to centralized exceptions.dart for reuse
- Throw WidgetUnmountedException instead of returning null when widget unmounts
- Handle WidgetUnmountedException separately in error handler with appropriate logging
- Still set _errorLoadingLocalThumbnail flag to prevent retry attempts

Using Exception instead of Error follows Dart conventions:
- Exceptions are for recoverable runtime conditions (like widget unmounting)
- Errors are for programming mistakes that shouldn't be caught

This ensures that widget unmounting is properly distinguished from actual
file access failures.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 14:17:49 +05:30
Ashil
fa86b19307 [mob][photos] Update internal change log (#7006) 2025-08-29 14:10:19 +05:30
Ashil
e632dc7771 Merge branch 'main' into update_int_change_log 2025-08-29 14:08:58 +05:30
ashilkn
7fa9adb636 update internal change log 2025-08-29 14:08:00 +05:30
Ashil
83f885f158 [mob][photos] Revert diskLoadDeferDuration to 500ms (#7005)
## Description
2025-08-29 13:58:58 +05:30
ashilkn
a295f223b6 Revert diskLoadDeferDuration to 500ms
Reverts the change from commit 1f1cad181f
which reduced galleryThumbnailDiskLoadDeferDuration from 500ms to 80ms.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 12:57:42 +05:30
Laurens Priem
cc64ef8035 [infra] Create more space for internal release action (#7004)
## Description

- Removed redundant SDKs
- Removed redundant rust install (already pre-installed)
- Delete old other action
2025-08-29 12:41:28 +05:30
Ashil
69dd7b6233 [mob][photos] Spacing (#7002)
## Description

Add spacing in similar images page.
2025-08-29 12:41:11 +05:30
Ashil
bcc9f1be73 [mob][photos] Revert cache extent changes (#7000)
## Description

The reverted changes were intended to solve the issue #6957 fixed. So
these changes are no longer needed and there are doubts if they are
causing regressions related to thumbnail loading.

## Tests
2025-08-29 12:26:29 +05:30
Ashil
296b2a2a6c Merge branch 'main' into revert-cache-extent-changes 2025-08-29 12:24:03 +05:30
laurenspriem
6b48c9bc34 Remove ineffective cleanup steps 2025-08-29 12:23:47 +05:30
ashilkn
6a951bcc72 Update internal change log 2025-08-29 12:23:28 +05:30
laurenspriem
38914981a1 Fix disk space calculation in cleanup step 2025-08-29 11:52:36 +05:30
laurenspriem
66f4d5b1a6 Add disk cleanup step to free space in GitHub Actions
Removes unused pre-installed software to free ~30-45GB:
- .NET SDK (~20-25GB)
- Haskell compiler (~5-8GB)
- Boost libraries (~1-2GB)
- Cached tool versions (~5-10GB)

Includes timing and space metrics for each removal

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-29 11:43:07 +05:30
laurenspriem
9ee3781320 spacing 2025-08-29 10:30:05 +05:30
ashilkn
23dc809589 Remove hardcoded cacheExtent to use Flutter's default value 2025-08-28 17:07:46 +05:30
ashilkn
f72c9fa068 Revert "Different cache extents for different photoGridSizes"
This reverts commit 769adb75c5.
2025-08-28 16:55:39 +05:30
Manav Rathi
0f5e30e96b feat(rust): Add metadata export matching Go CLI format
Export album and file metadata to .meta folders within each album directory.
Enables incremental sync and compatibility with Go CLI exports.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 17:22:17 +05:30
Manav Rathi
35ded7bc59 fix(rust): Match Go CLI's album-based export directory structure
Switch from date-based (YYYY/MM-Month) to album-based directory structure
to ensure compatibility with Go CLI. Files now export to AlbumName/ folders
with "Uncategorized" for files without albums.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 17:06:40 +05:30
Manav Rathi
8e3f6e56d2 feat(rust): Remove sync command to match Go CLI interface
Align with Go CLI by integrating sync into export workflow.
Update CLAUDE.md to prevent default template usage in commits.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 16:51:04 +05:30
Manav Rathi
150534aa1a feat(rust): Add deduplication, live photos, and update docs
- Hash-based file deduplication prevents duplicate exports
- Live photo extraction from ZIP archives
- Update conversion status documenting feature completion
- Make commit guidelines prominent in CLAUDE.md
- Remove redundant commit format section

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-27 07:53:22 +05:30
Manav Rathi
2a136ba087 fix(rust): Fix file counting logic in sync and export commands
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 21:46:52 +05:30
Manav Rathi
3abb479fbf feat(rust): Add progress indicators for downloads
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 21:26:39 +05:30
Manav Rathi
7eda60a493 fix(rust): Fix incremental sync to properly track per-collection timestamps
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 21:15:11 +05:30
Manav Rathi
bb8c5caa8d feat(rust): Handle renamed files using public magic metadata
Check both public magic metadata (for edited names) and regular metadata
when determining file names during export and sync, matching Go CLI behavior

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 21:00:11 +05:30
Manav Rathi
0384819c01 Take 2 2025-08-26 18:06:05 +05:30
Manav Rathi
f55973367d feat(rust): Add retry logic and export filters
- Add configurable retry with exponential backoff for API calls
- Handle 429 and 5xx errors with automatic retries
- Add export filters for albums, shared, and hidden collections
- Fix formatting and clippy warnings to pass CI checks

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 17:54:57 +05:30
Manav Rathi
699794226f fix(rust): Fix sync command file downloads
- Handle non-interactive mode in account add command
- Fix cross-filesystem file move issue by using copy+delete instead of rename
- Successfully tested downloading files from local server

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 16:00:15 +05:30
Manav Rathi
dee68acfc3 docs(rust): Reduce verbosity
Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 15:31:06 +05:30
eYdr1en
0bd5452837 Merge remote-tracking branch 'upstream/main' into touch-id 2025-08-26 11:07:30 +02:00
Manav Rathi
e53ddb8b51 refactor(rust): Remove backward compatibility code
Since the CLI hasn't been released yet, we don't need to maintain
backward compatibility. This commit removes unnecessary compatibility
code to simplify the codebase.

Changes:
- Remove id field from Account struct (use user_id directly)
- Remove update_file_local_path legacy wrapper method
- Use mark_file_synced directly instead of the wrapper
- Update all references from account.id to account.user_id

This results in cleaner, more maintainable code without unnecessary
compatibility layers.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 10:12:26 +05:30
Manav Rathi
95d167878e refactor(rust): Eliminate redundant primary keys using global uniqueness
Since user_id is globally unique in Ente's system (like collection_id and
file_id), we can eliminate artificial primary keys and use the actual IDs
directly. This simplifies the schema and reduces redundancy.

Changes:
- Use (user_id, app) composite primary key in accounts table
- Use (user_id, app) composite primary key in secrets table
- Remove account_id references, use user_id directly
- Update collections table to use owner field (user_id)
- Update files table to use owner_id field (user_id)
- Remove account_id from album_files table
- Update sync_state table to use (user_id, app) primary key
- Update all storage methods to use new schema
- Update commands to pass correct parameters to storage methods
- Update indices for better query performance

This aligns with Ente's API design where these IDs are guaranteed to be
globally unique, eliminating the need for artificial primary keys.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 10:04:24 +05:30
Manav Rathi
653fc47aed fix(rust): Fix clippy warning for collapsible if statement
Collapsed nested if statement in sync.rs to satisfy clippy's
collapsible-if lint rule. This change is required for CI to pass
with the updated Rust version.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:43:21 +05:30
Manav Rathi
34325691e7 refactor(rust): Use collection_id and file_id as primary keys
Since collection_id and file_id are globally unique across all users in
Ente's API, we can use them directly as primary keys instead of creating
artificial auto-increment IDs. This simplifies the schema and reduces
redundancy.

Changes:
- Use collection_id as primary key in collections table
- Use file_id as primary key in files table
- Use composite primary key (album_id, file_id) in album_files table
- Update all related SQL queries to match new schema
- Add appropriate foreign key constraints
- Optimize indices for the new structure

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:30:05 +05:30
Manav Rathi
e474114e22 fix(rust): Fix clippy warnings and improve CI documentation
- Fix collapsible if statement warnings in sync.rs and files.rs
- Update CLAUDE.md with clearer CI requirements
- Remove misleading auto-fix command that doesn't catch all issues
- Emphasize that ALL checks must pass before committing

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 09:14:33 +05:30
Manav Rathi
80c07d36a9 feat(rust): Complete sync command with file downloads
- Integrated DownloadManager with sync command for actual file downloads
- Implemented proper sync state tracking using is_synced_locally flag
- Fixed database persistence by preserving sync state during updates
- Added proper collection key decryption for file downloads
- Files are only downloaded once and marked as synced
- Cleaned up schema - removed migrations since this is new code
- Fixed deserialization issues with RemoteFile thumbnail field
- Added proper error handling for missing collection keys

The sync command now:
1. Fetches metadata for collections and files
2. Downloads files that haven't been synced yet
3. Marks files as synced to avoid re-downloading
4. Properly handles existing files on disk

This matches the Go CLI's approach of using a synced flag rather than
checking file existence on every sync.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 06:27:09 +05:30
Manav Rathi
8581742a73 feat(rust): Integrate DownloadManager with sync command
- Added local_path column to files table for tracking downloaded files
- Implemented get_pending_downloads() to find files without local_path
- Integrated DownloadManager into sync command for full file downloads
- Added collection key decryption for file downloads
- Generate proper export paths with date/album structure
- Track successful downloads and update database with local paths
- Added migration to add local_path column to existing databases

The sync command now supports full file downloads (not just metadata).
Files are downloaded to the export directory with proper organization.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 05:55:03 +05:30
Manav Rathi
042dae8790 fix(rust): Apply cargo fmt and clippy fixes
- Fixed formatting issues in sync/engine.rs
- Added #[allow(dead_code)] for unused storage field in DownloadManager
- Replaced manual clamp with .clamp() method

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-26 05:41:33 +05:30
Neeraj Gupta
bc6506cb10 Fix link for locker 2025-08-25 10:50:05 +05:30
Neeraj Gupta
f2a26ba391 Minor refactor 2025-08-25 10:35:47 +05:30
Manav Rathi
84f5a5ac3d feat(rust): Add sync command and fix database path
- Add new `sync` command to fetch collections and file metadata
- Change config directory from ~/.ente/ to ~/.config/ente-cli/ to avoid conflicts with Go CLI
- Fix sync engine to use correct API endpoints (/collections/v2/diff instead of /diff)
- Implement per-collection file syncing matching Go CLI behavior
- Fix foreign key constraints in database schema
- Add metadata-only and full sync options
- Store database path in Storage struct for creating new instances
- Successfully tested with real account: syncs 5 files and exports correctly

The sync command now properly fetches all collections and files from the API,
storing them in SQLite for offline access and incremental sync support.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 16:47:08 +05:30
Manav Rathi
a00fc0b1be fix(rust): Remove sensitive information from logs and docs
Security improvements:
- Remove all debug logs that output tokens, keys, or credentials
- Remove email addresses from debug output
- Remove encrypted keys and nonces from logs
- Remove specific account references from documentation
- Add security guidelines to CLAUDE.md

No sensitive information (PII, credentials, tokens) should be logged
even in debug mode. Updated guidelines to prevent future occurrences.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 15:06:28 +05:30
Manav Rathi
f5347e7436 docs(rust): Update conversion plan with completed features
- Mark streaming XChaCha20-Poly1305 implementation as complete
- Document successful export functionality with all decryption working
- Update testing status with successful real account exports
- Add recent achievements section highlighting key milestones
- Update feature parity progress checklist
- Document what components are complete vs remaining

The export functionality is now fully working with proper decryption
of collections, files, and metadata. Updated PR description as well.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 14:26:09 +05:30
Manav Rathi
3f1d574d0c feat(rust): Add progress indicators to export
- Show progress for each exported file with count
- Improve export summary with emojis and better formatting
- Add contextual success messages based on export results
- Make export output more user-friendly

The export now provides clear feedback during the process and
a helpful summary at the end.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 14:14:59 +05:30
Manav Rathi
891b68c0f4 fix(rust): Add chunked decryption for large files
- Implement chunked streaming decryption matching Go's 4MB buffer size
- Update file decryption to use decrypt_file_data instead of decrypt_stream
- Successfully tested with 33MB RAW image file
- All test files now decrypt correctly

Large files are now properly handled with chunked decryption, preventing
memory issues and matching the Go implementation's behavior.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 14:12:06 +05:30
Manav Rathi
f050c6f9d7 feat(rust): Implement streaming XChaCha20-Poly1305 decryption
- Add streaming cipher module using libsodium's secretstream API
- Update file and metadata decryption to use streaming XChaCha20-Poly1305
- Fix decryption issues - files now properly decrypt
- Successfully tested with real account - exports working for smaller files

The export now correctly decrypts files using the same streaming cipher
as the Go implementation. Large files may need chunked decryption support.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 14:01:59 +05:30
Manav Rathi
2de67b619f feat(rust): Add metadata decryption for original filenames
- Create metadata module with FileMetadata struct
- Decrypt file metadata to extract original filename
- Use original filename in export path generation
- Add proper file type detection from metadata
- Implement filename sanitization for filesystem safety

Files are now exported with their original names instead of generic IDs.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 13:25:38 +05:30
Manav Rathi
828dde5ca7 feat(rust): Implement file decryption in export command
- Add ChaCha20-Poly1305 decryption for downloaded files
- Decrypt file keys using master key
- Extract nonce from encrypted file data
- Add basic filename generation with extension detection
- Comment out sync modules temporarily due to model mismatches

The export command now properly decrypts files instead of saving them encrypted.
Next steps: extract original filenames from decrypted metadata.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 13:03:46 +05:30
Manav Rathi
2526c69896 docs(rust): Update pre-commit checklist to match CI configuration
Ensure clippy commands use --all-targets --all-features flags to match
the CI environment exactly. This prevents CI failures from warnings that
weren't caught locally.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 12:09:42 +05:30
Manav Rathi
6e64a2067f fix(rust): Resolve all clippy warnings for CI compliance
- Fix lifetime elision warnings in storage/mod.rs by adding explicit lifetimes
- Collapse nested if-let statements in export.rs using let-chains
- Code now passes: cargo clippy --all-targets --all-features

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 09:58:29 +05:30
Manav Rathi
ab4792518f docs(rust): Add mandatory pre-commit checklist to CLAUDE.md
Add explicit pre-commit commands that must be run before every commit
to ensure CI passes. These commands simulate the CI environment locally.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 09:29:28 +05:30
Manav Rathi
d4ae8d63fc fix(rust): Fix linting and formatting issues for CI
- Applied cargo fmt to ensure consistent formatting
- Fixed all clippy warnings (uninlined_format_args)
- Code now passes all CI checks with RUSTFLAGS="-D warnings"

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-23 09:01:56 +05:30
Manav Rathi
618753cb1a feat(rust): Implement export command with collection-based file fetching
- Fix token encoding to use base64 URL with padding (matching Go implementation)
- Add export command that iterates through collections and fetches files
- Update API models to handle actual server response field names (ownerID vs ownerId)
- Fix file download URLs for local/dev environments
- Implement proper directory structure creation (YYYY/MM-Month format)
- Add collection attributes and public URL models for complete API compatibility
- Successfully exports encrypted files from both local and production endpoints

The export command now:
- Fetches all collections for an account
- Iterates through each collection to get files
- Downloads encrypted files and saves them to the export directory
- Skips already downloaded files to support incremental exports

Note: Files are still encrypted; decryption will be implemented in a future commit.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 21:13:52 +05:30
Manav Rathi
f84bd20bbf feat(rust): Store API endpoint per account for better environment isolation
- Add endpoint field to accounts database table with default to production API
- Update Account model to include endpoint field
- Add --endpoint flag to account add command only
- Remove ENTE_ENDPOINT environment variable support
- Update account list to display endpoints in readable format
- Each account now maintains its own endpoint, preventing confusion between test and production environments

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 20:25:39 +05:30
Manav Rathi
6ae7aa70d6 fix(rust): Fix FFI type cast and clippy warnings for CI
- Use std::ffi::c_char for libsodium FFI context parameter cast
- Fix all clippy warnings to pass CI with RUSTFLAGS="-D warnings"
- Update CLAUDE.md with FFI casting guidance for future development

This ensures the code passes all CI checks including the stricter
clippy settings used in GitHub Actions.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 18:49:24 +05:30
Manav Rathi
48757af5d0 fix(rust): Fix SRP authentication implementation
Fixed issues preventing successful authentication:
- Corrected Argon2 memory limit handling (API sends bytes, not KB)
- Replaced Blake2b with crypto_kdf_derive_from_key for login subkey derivation
- Fixed serde field names to match API expectations (srpUserID, sessionID)
- Added non-interactive mode for CLI testing
- Added support for ENTE_ENDPOINT environment variable

The implementation now matches the web client's key derivation exactly,
enabling successful authentication with both local and production servers.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 18:23:16 +05:30
Manav Rathi
cd20a98850 feat(rust): Implement account management commands
Add comprehensive account management functionality with secure SRP authentication.
This enables users to add, list, update, and manage multiple Ente accounts
for photos, locker, and auth apps.

Key features:
- Complete account add flow with SRP authentication
- Two-factor authentication support (TOTP)
- Secure key decryption and storage
- Multi-account support with per-app configuration
- Account list and update commands
- Export directory management
- Interactive CLI prompts with dialoguer

The implementation integrates with the API client for authentication and
securely stores account credentials in SQLite.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 13:57:29 +05:30
Manav Rathi
9ac9e6bd26 feat(rust): Implement comprehensive API client for Ente services
Add complete API client implementation with authentication, file operations,
and collection management. This enables the Rust CLI to interact with Ente
servers for photo backup and sync operations.

Key features:
- Multi-account token management with secure storage
- SRP authentication flow matching Go implementation
- Retry logic with exponential backoff for network resilience
- Full API coverage: auth, collections, files, trash, user details
- Request/response models for all API endpoints
- Separate download client for large file transfers
- Smart CDN routing for production file downloads

The implementation follows the conversion plan and maintains compatibility
with the existing Go CLI API patterns.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 12:14:05 +05:30
Manav Rathi
0b640c9062 docs(rust): Enhance CLAUDE.md with comprehensive codebase guidance
Add detailed development commands, architecture overview, and module
descriptions to help future Claude instances understand the codebase
structure and development workflow.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 10:38:57 +05:30
Manav Rathi
2d87aba165 docs(rust): Add comprehensive conversion plan
- Document current implementation status
- Detail API client implementation steps
- List all remaining components with specifications
- Include testing strategy and migration notes
- Provide file structure reference for navigation
- Add implementation guidelines and environment variables

This plan enables any developer to understand the project state
and continue the conversion work from the current point.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 10:03:01 +05:30
Manav Rathi
7dffdfaecf feat(rust): Implement SQLite storage layer
- Replace sled with SQLite for better reliability and tooling
- Create schema with tables for accounts, secrets, collections, files, and sync state
- Implement account storage with multi-account support
- Add configuration and sync state management
- Support for storing encrypted credentials separately
- Add indices for performance optimization

SQLite provides ACID transactions, better debugging tools, and a proven
track record for reliability with user data.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-22 09:50:59 +05:30
Manav Rathi
a4da7b5555 main safeguard 2025-08-22 09:26:55 +05:30
Manav Rathi
85b766b5d0 Safeguard 2025-08-21 16:31:35 +05:30
Manav Rathi
62f715d3c1 fix(rust): Use std::ffi::c_char for FFI type casting
- Replace libc::c_char with std::ffi::c_char for password parameter
- Remove unnecessary libc dependency
- Use standard library FFI types (available since Rust 1.64)

This fixes the CI build error where libsodium expects *const c_char
for the password parameter in crypto_pwhash.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 16:27:50 +05:30
Manav Rathi
e35ae86fa5 Ask it to run cargo fmt etc
For the current session cc was able to use that instruction to figure out all the linters etc to run. If that doesn't work in future sessions, we can use a longer instruction, something like what it itself suggested

    ## CI/CD Requirements
    - Must pass `cargo fmt --check`
    - Must pass `cargo clippy --all-targets --all-features`
    - Must pass `RUSTFLAGS="-D warnings" cargo build`
    - Fix all formatting before committing
    - Address all clippy warnings
    - Use `#![allow(dead_code)]` during development for unused code

    ## Code Quality
    - Run `cargo fmt` before committing
    - Fix clippy warnings: remove unnecessary casts, use idiomatic Rust
    - Prefix unused variables with underscore
    - Remove unused imports
2025-08-21 12:33:14 +05:30
Manav Rathi
ea843eba7a fix(rust): Address cargo fmt and clippy warnings
- Fix code formatting with cargo fmt
- Remove unnecessary type casts
- Use range contains instead of manual comparison
- Prefix unused variables with underscore
- Remove unused imports
- Add allow(dead_code) for development phase

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 12:24:04 +05:30
Manav Rathi
b845f4d893 Keep co-author but remove the self promo link
Typical Claude Code commit message:
feat: implement user authentication

- Added login endpoint
- Implemented JWT tokens
- Created middleware

Created with Claude Code: https://claude.ai/code  # <-- The promotional link
Co-authored-by: Claude <claude@anthropic.com>      # <-- The co-author line

This memory is to ask claude to keep the co-author line but remove the self promo from the commit message it creates.
2025-08-21 06:51:22 +05:30
Manav Rathi
8ea36acb7a feat(rust): Initialize Rust CLI foundation with libsodium crypto
- Set up project structure mirroring Go CLI architecture
- Add dependencies with libsodium-sys-stable for all crypto operations
- Implement core crypto module with Argon2, ChaCha20-Poly1305, and Blake2b
- Create data models for accounts, files, and collections
- Set up Clap-based CLI framework with account, export, and version commands
- Add error handling with thiserror
- Configure for static linking to create standalone binaries

This establishes the foundation for converting the Ente CLI from Go to Rust,
with a focus on maintaining compatibility with existing libsodium-based crypto.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-21 06:50:23 +05:30
eYdr1en
279df8ff57 fixes lint error 2025-08-13 12:52:13 +02:00
Adrián Horváth
d83994c692 Update mobile/apps/auth/lib/ui/tools/lock_screen.dart
Co-authored-by: Prateek Sunal <prtksunal@gmail.com>
2025-08-13 12:37:11 +02:00
eYdr1en
be506bdad1 fixes macos touch id lock 2025-08-08 17:22:12 +02:00
ashilkn
5cff5f49b7 Merge branch 'release_mob_jul_31' into f-droid 2025-08-07 14:05:16 +05:30
ashilkn
3d90e37a00 Merge gallery_scroll_improvement 2025-08-07 13:19:15 +05:30
ashilkn
eb9f5830a5 Merge branch 'gallery_scroll_improvement' into release_mob_jul_31 2025-08-07 13:15:05 +05:30
ashilkn
348ede2a03 bump up build number 2025-08-05 20:36:39 +05:30
ashilkn
5619b349b3 Merge branch 'increase_cache_extent_gallery' into release_mob_jul_31 2025-08-05 20:32:09 +05:30
ashilkn
03cb74b90b Merge release_mob_jul_31 2025-07-31 23:12:41 +05:30
Neeraj Gupta
2bddf7fcef Merge branch 'release_mob_jun_25_hotfix' into f-droid 2025-07-14 15:31:06 +05:30
Neeraj Gupta
86421d5472 Bump version 2025-07-14 11:11:44 +05:30
Neeraj Gupta
4359c3f071 Merge remote-tracking branch 'origin' into release_mob_jun_25_hotfix 2025-07-14 11:10:58 +05:30
Neeraj Gupta
bab9d89d88 Bump version 2025-07-11 12:49:31 +05:30
Neeraj Gupta
f7f0afdca6 Merge branch 'release_mob_jun_25' into f-droid 2025-07-02 21:37:47 +05:30
Neeraj Gupta
f7f1227fa4 Bump version 2025-07-02 14:14:58 +05:30
Neeraj Gupta
5ea93c0935 Merge branch 'release_mob_jun_25' of https://github.com/ente-io/auth into release_mob_jun_25 2025-07-02 14:14:00 +05:30
Neeraj Gupta
943fffbcf9 Merge branch 'main' of https://github.com/ente-io/auth into release_mob_jun_25 2025-07-02 14:13:50 +05:30
Prateek Sunal
058543b287 chore: bump version 2025-07-01 19:11:36 +05:30
Prateek Sunal
dff77233db Merge remote-tracking branch 'origin/widget' into release_mob_jun_25 2025-07-01 19:11:24 +05:30
Prateek Sunal
dab331535b chore: bump version 2025-07-01 18:39:21 +05:30
Prateek Sunal
3873946046 Merge remote-tracking branch 'origin/widget' into release_mob_jun_25 2025-07-01 18:39:04 +05:30
Neeraj Gupta
0f13558ae3 Merge branch 'main' into release_mob_jun_25 2025-07-01 16:55:08 +05:30
Neeraj Gupta
a2bb263c49 Bump version 2025-07-01 16:32:56 +05:30
ashilkn
6d70678377 Remove stale data 2025-06-04 18:17:49 +05:30
ashilkn
f08ccce1e3 Resolve merge conflicts and merge release/2025-05-30 2025-05-30 09:36:57 +05:30
ashilkn
4e3040a36b Remove store data and media related to playstore (mobile/fastlane/metadata/playstore/en-US) for f-droid 2025-05-12 07:59:01 +05:30
Neeraj Gupta
16345fa7a1 Merge branch 'release_mob_5may2025' into f-droid 2025-05-09 15:30:58 +05:30
ashilkn
c0d88a2cac Merge branch 'main' into f-droid 2025-04-25 15:44:29 +05:30
Neeraj Gupta
809ce0f24a Merge branch 'mob_release_15' into f-droid 2025-04-23 12:49:15 +05:30
Neeraj Gupta
cbd22523fd Merge branch 'f-droid' of https://github.com/ente-io/auth into f-droid 2025-04-23 12:02:37 +05:30
Neeraj Gupta
85d39dc097 ios build changes 2025-04-16 10:45:26 +05:30
ashilkn
703f2a67f8 Update f-droid store app data 2025-04-14 17:08:49 +05:30
Neeraj Gupta
68c2fbfec6 Bump version 2025-03-27 14:01:46 +05:30
Neeraj Gupta
fd3bcbf2a8 Add flutter submodule 2025-03-27 14:00:54 +05:30
ashilkn
78077e70c6 Update fdoird store listing metadata following the fastlane structure
Note: Not certain that this is the right way to update listing metadata. Read through this PR for a better idea why: https://github.com/ente-io/ente/pull/1313
2025-03-26 15:25:56 +05:30
ashilkn
04e2fd0262 Resolve merge conflicts and merge tag 'photos-v1.0.0' to f-droid branch 2025-03-26 12:23:17 +05:30
Neeraj Gupta
b377217ece Merge branch 'mob_6_march' into f-droid 2025-03-10 15:10:56 +05:30
Neeraj Gupta
7242176243 [mob] Bump version code 2025-03-06 15:46:52 +05:30
Neeraj Gupta
b3123a6440 Merge branch '0.9.98_release_branch' into f-droid 2025-02-14 20:07:06 +05:30
ashilkn
f4eb511beb Merge tag 'photos-v0.9.97' into f-droid 2025-02-12 22:07:36 +05:30
Neeraj Gupta
1a689b2c19 Merge branch 'main' into f-droid 2025-02-10 14:29:21 +05:30
Neeraj Gupta
b0c6ffdbb2 Merge branch 'main' into f-droid 2025-01-15 13:06:56 +05:30
Neeraj Gupta
b7ccf4aaf9 Merge branch 'f-droid' of https://github.com/ente-io/auth into f-droid 2025-01-15 13:06:47 +05:30
ashilkn
e7c8265ae1 Merge branch 'main' into f-droid 2025-01-08 12:39:54 +05:30
ashilkn
21dc35355d Merge branch 'main' into f-droid 2025-01-03 18:40:49 +05:30
ashilkn
f86994b1d3 Merge tag 'photos-v0.9.72' into f-droid 2024-12-20 11:44:00 +05:30
Neeraj Gupta
260a26d45c Merge branch 'main' into f-droid 2024-12-11 21:58:29 +05:30
ashilkn
cdfa368a8c Merge branch 'main' into f-droid 2024-12-09 12:51:05 +05:30
Neeraj Gupta
d67c6aef53 Merge branch 'main' into f-droid 2024-11-28 11:01:39 +05:30
Neeraj Gupta
6ebb5d5bf4 Merge branch 'f-droid' of https://github.com/ente-io/auth into f-droid 2024-11-28 11:00:11 +05:30
ashilkn
224b79b648 Merge tag 'photos-v0.9.58' into f-droid 2024-11-08 16:08:08 +05:30
Neeraj Gupta
7e0a3cdd6c Merge branch 'main' into f-droid 2024-10-24 13:29:54 +05:30
ashilkn
f6db381e20 [mob][photos] Resolve merge conflicts and merge main 2024-10-23 11:25:54 +05:30
ashilkn
f0c29fef5c Merge branch 'main' into f-droid 2024-10-16 17:06:01 +05:30
Neeraj Gupta
2a3e317725 Merge branch 'main' into f-droid 2024-10-15 21:01:21 +05:30
ashilkn
1a1b3ebf12 [mob][photos] Resolve merge conflicts and merge main 2024-10-09 13:52:19 +05:30
Neeraj Gupta
f995589a02 Merge branch 'main' into f-droid 2024-09-29 12:04:26 +05:30
Neeraj Gupta
6e0990d658 Merge branch 'main' into f-droid 2024-09-20 15:56:08 +05:30
Neeraj Gupta
4da4261f4c Update flutter to 3.24.3 2024-09-20 15:00:23 +05:30
Neeraj Gupta
0abe66ea8c Merge branch 'main' into f-droid 2024-09-20 14:49:17 +05:30
Neeraj Gupta
193b27a186 Merge commit '0a1e062c' into f-droid 2024-09-06 15:30:52 +05:30
Neeraj Gupta
e323096172 Merge tag 'photos-v0.9.30' into f-droid 2024-08-27 17:20:23 +05:30
ashilkn
e41f306ac8 [mob][photos] Resolve merge conflicts and merge main 2024-07-31 12:02:25 +05:30
Neeraj Gupta
01d45d7c14 Merge branch 'main' into f-droid 2024-07-19 15:53:08 +05:30
ashilkn
d55a29336f Merge branch 'main' into f-droid 2024-07-08 20:50:35 +05:30
Neeraj Gupta
cfcbd0fbb2 Merge branch 'f-droid' of https://github.com/ente-io/auth into f-droid 2024-06-17 11:47:58 +05:30
Neeraj Gupta
21174548b5 Merge branch 'main' into f-droid 2024-06-17 11:47:42 +05:30
Neeraj Gupta
910f13e9a8 [mob][fdroid] Update flutter to v3.22.0 2024-06-17 11:31:36 +05:30
ashilkn
762688db28 Merge branch 'main' into f-droid 2024-06-13 10:29:55 +05:30
ashilkn
9df1ea0c57 Merge branch 'main' into f-droid 2024-06-12 17:33:12 +05:30
ashilkn
e48ab71fa4 [mob][photos] f-droid: upgrade flutter submodule to version 3.22.2 2024-06-12 17:33:02 +05:30
ashilkn
246314367a [mob][photos] Update flutter submodule on f-droid 2024-06-04 13:14:24 +05:30
ashilkn
ad70bbb571 Merge branch 'main' into f-droid 2024-06-04 13:11:17 +05:30
Neeraj Gupta
3962c55140 Update flutter submodule: v3.22.0 2024-06-03 11:26:02 +05:30
Neeraj Gupta
82e478bb12 Merge branch 'f-droid' of https://github.com/ente-io/auth into f-droid 2024-06-03 11:25:26 +05:30
Neeraj Gupta
63c8e98492 Merge branch 'main' into f-droid 2024-06-03 11:21:35 +05:30
ashilkn
ae92d2f759 Merge branch 'main' into f-droid 2024-05-28 12:37:14 +05:30
ashilkn
761c3e6ac2 [mob][photos] Update flutter submodule on f-droid branch 2024-05-28 12:34:37 +05:30
ashilkn
f9a3009c60 [mob][photos] Resolve merge conflicts and merge 2024-05-28 12:28:03 +05:30
Neeraj Gupta
ca0474faca Updated submodule mobile/thirdparty/flutter to 3.22.1 2024-05-23 17:00:33 +05:30
Neeraj Gupta
b469985277 Removed submodule mobile/thirdparty/isar 2024-05-23 16:58:51 +05:30
Neeraj Gupta
2a5dacb460 Merge branch 'main' into f-droid 2024-05-23 16:55:27 +05:30
vishnukvmd
d16f98cf07 v0.8.95 2024-05-12 08:44:26 +05:30
vishnukvmd
8677cbb4f8 Increase JVM allocation pool 2024-05-12 08:43:55 +05:30
vishnukvmd
0e33299863 Merge branch 'main' into f-droid 2024-05-07 12:54:44 +05:30
ashilkn
93ba4e011a Merge branch 'main' into f-droid 2024-04-20 15:23:14 +05:30
vishnukvmd
7977bebcaa Update Flutter to v3.19.3 2024-04-16 11:35:32 +05:30
ashilkn
f28f49d724 Merge main 2024-04-15 11:20:03 +05:30
ashilkn
d9a93ddad6 Merge branch 'main' into f-droid 2024-04-13 15:24:56 +05:30
ashilkn
07808d6139 Merge branch 'main' into f-droid 2024-04-02 17:22:34 +05:30
vishnukvmd
1e1633bb45 Merge branch 'main' into f-droid 2024-03-13 21:57:19 +05:30
vishnukvmd
c0f33de0c8 Remove dead code 2024-03-13 21:56:09 +05:30
vishnukvmd
417621b17c Pull code for transistor-background-fetch 2024-03-13 14:14:19 +05:30
vishnukvmd
8322540732 Add submodule for Flutter 2024-03-13 14:13:40 +05:30
vishnukvmd
2d61be37bb Add submodule for Isar 2024-03-13 14:12:23 +05:30
vishnukvmd
2a10aa7d61 Merge branch 'fdroid_cleanup' into f-droid 2024-03-13 13:52:25 +05:30
vishnukvmd
004eb310b3 Prepare for F-Droid 2024-03-13 13:43:46 +05:30
206 changed files with 15709 additions and 874 deletions

View File

@@ -27,6 +27,38 @@ jobs:
with:
submodules: recursive
- name: Free up disk space
run: |
echo "Initial disk usage:"
df -h /
# Get available space in KB
INITIAL=$(df / | awk 'NR==2 {print $4}')
echo -e "\n=== Removing .NET SDK (~20-25GB) ==="
BEFORE=$(df / | awk 'NR==2 {print $4}')
START=$(date +%s)
sudo rm -rf /usr/share/dotnet
END=$(date +%s)
AFTER=$(df / | awk 'NR==2 {print $4}')
FREED=$(( (AFTER - BEFORE) / 1048576 )) # Convert KB to GB
echo "Time: $((END-START))s | Freed: ${FREED}GB"
echo -e "\n=== Removing cached tools (~5-10GB) ==="
BEFORE=$(df / | awk 'NR==2 {print $4}')
START=$(date +%s)
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
END=$(date +%s)
AFTER=$(df / | awk 'NR==2 {print $4}')
FREED=$(( (AFTER - BEFORE) / 1048576 ))
echo "Time: $((END-START))s | Freed: ${FREED}GB"
echo -e "\n=== Final Summary ==="
FINAL=$(df / | awk 'NR==2 {print $4}')
TOTAL_FREED=$(( (FINAL - INITIAL) / 1048576 ))
echo "Total space freed: ${TOTAL_FREED}GB"
echo "Final disk usage:"
df -h /
- name: Setup JDK 17
uses: actions/setup-java@v1
with:

View File

@@ -28,6 +28,38 @@ jobs:
with:
submodules: recursive
- name: Free up disk space
run: |
echo "Initial disk usage:"
df -h /
# Get available space in KB
INITIAL=$(df / | awk 'NR==2 {print $4}')
echo -e "\n=== Removing .NET SDK (~20-25GB) ==="
BEFORE=$(df / | awk 'NR==2 {print $4}')
START=$(date +%s)
sudo rm -rf /usr/share/dotnet
END=$(date +%s)
AFTER=$(df / | awk 'NR==2 {print $4}')
FREED=$(( (AFTER - BEFORE) / 1048576 )) # Convert KB to GB
echo "Time: $((END-START))s | Freed: ${FREED}GB"
echo -e "\n=== Removing cached tools (~5-10GB) ==="
BEFORE=$(df / | awk 'NR==2 {print $4}')
START=$(date +%s)
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
END=$(date +%s)
AFTER=$(df / | awk 'NR==2 {print $4}')
FREED=$(( (AFTER - BEFORE) / 1048576 ))
echo "Time: $((END-START))s | Freed: ${FREED}GB"
echo -e "\n=== Final Summary ==="
FINAL=$(df / | awk 'NR==2 {print $4}')
TOTAL_FREED=$(( (FINAL - INITIAL) / 1048576 ))
echo "Total space freed: ${TOTAL_FREED}GB"
echo "Final disk usage:"
df -h /
- name: Setup JDK 17
uses: actions/setup-java@v1
with:
@@ -40,6 +72,12 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install Flutter Rust Bridge
run: cargo install flutter_rust_bridge_codegen
- name: Generate Rust bindings
run: flutter_rust_bridge_codegen generate
- name: Setup keys
uses: timheuer/base64-to-file@v1
with:

3
.gitmodules vendored
View File

@@ -9,3 +9,6 @@
[submodule "auth/assets/simple-icons"]
path = mobile/apps/auth/assets/simple-icons
url = https://github.com/simple-icons/simple-icons.git
[submodule "mobile/thirdparty/flutter"]
path = mobile/thirdparty/flutter
url = https://github.com/flutter/flutter.git

View File

@@ -48,7 +48,11 @@ See [docs/](docs/README.md) for how to edit these documents.
## Code contributions
If you'd like to contribute code, it is best to start small. Consider some well-scoped changes, say like adding more [custom icons to auth](mobile/apps/auth/docs/adding-icons.md), or fixing a specific bug.
If you'd like to contribute code, it is best to start small. Consider some well-scoped changes, say like adding more [custom icons to auth](mobile/apps/auth/docs/adding-icons.md), or fixing a specific bug. There is a (possibly outdated) list of tasks with the ["help wanted" or "good first issue"](<https://github.com/ente-io/ente/issues?q=state%3Aopen%20(label%3A%22good%20first%20issue%22%20OR%20label%3A%22help%20wanted%22%20)>) label too.
If you use any form of AI assistance, please include a co-author attribution in the commit for transparency.
In your PR, please include before / after screenshots, and clearly indicate the tests that you performed.
Code that changes the behaviour of the product might not get merged, at least not initially. The PR can serve as a discussion bed, but you might find it easier to just start a discussion instead, or post your perspective in the (likely) existing thread about the behaviour change or new feature you wish for.

View File

@@ -1236,6 +1236,12 @@
"title": "Parqet",
"slug": "parqet"
},
{
"title": "Parallels",
"slug": "parallels",
"hex": "#E61E25",
"altNames": ["Parallels Desktop", "Parallels VM"]
},
{
"title": "Parsec"
},

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect x="20" y="10" width="10" height="80" rx="5" fill="#E61E25"/>
<rect x="50" y="10" width="10" height="80" rx="5" fill="#E61E25"/>
</svg>

After

Width:  |  Height:  |  Size: 207 B

View File

@@ -382,7 +382,8 @@ class _HomePageState extends State<HomePage> {
final bool shouldShowLockScreen =
await LockScreenSettings.instance.shouldShowLockScreen();
if (shouldShowLockScreen) {
await AppLock.of(context)!.showLockScreen();
// Manual lock: do not auto-prompt Touch ID; wait for user tap
await AppLock.of(context)!.showManualLockScreen();
} else {
await showDialogWidget(
context: context,

View File

@@ -7,8 +7,7 @@ import 'package:ente_events/event_bus.dart';
import 'package:ente_events/models/signed_in_event.dart';
import 'package:ente_events/models/signed_out_event.dart';
import 'package:ente_strings/l10n/strings_localizations.dart';
import 'package:ente_ui/theme/colors.dart';
import 'package:ente_ui/theme/ente_theme_data.dart';
import "package:ente_ui/theme/ente_theme_data.dart";
import 'package:ente_ui/utils/window_listener_service.dart';
import 'package:flutter/foundation.dart';
import "package:flutter/material.dart";
@@ -87,37 +86,14 @@ class _AppState extends State<App>
@override
Widget build(BuildContext context) {
final schemes = ColorSchemeBuilder.fromCustomColors(
primary700: const Color(0xFF1565C0), // Dark blue
primary500: const Color(0xFF2196F3), // Material blue
primary400: const Color(0xFF42A5F5), // Light blue
primary300: const Color(0xFF90CAF9), // Very light blue
iconButtonColor: const Color(0xFF1976D2), // Custom icon color
gradientButtonBgColors: const [
Color(0xFF1565C0),
Color(0xFF2196F3),
Color(0xFF42A5F5),
],
);
final lightTheme = createAppThemeData(
brightness: Brightness.light,
colorScheme: schemes.light,
);
final darkTheme = createAppThemeData(
brightness: Brightness.dark,
colorScheme: schemes.dark,
);
Widget buildApp() {
if (Platform.isAndroid ||
Platform.isWindows ||
Platform.isLinux ||
kDebugMode) {
return AdaptiveTheme(
light: lightTheme,
dark: darkTheme,
light: lightThemeData,
dark: darkThemeData,
initial: AdaptiveThemeMode.system,
builder: (lightTheme, dartTheme) => MaterialApp(
title: "ente",
@@ -142,8 +118,8 @@ class _AppState extends State<App>
return MaterialApp(
title: "ente",
themeMode: ThemeMode.system,
theme: lightTheme,
darkTheme: darkTheme,
theme: lightThemeData,
darkTheme: darkThemeData,
debugShowCheckedModeBanner: false,
locale: locale,
supportedLocales: appSupportedLocales,

View File

@@ -10,6 +10,7 @@ import 'package:ente_lock_screen/ui/lock_screen.dart';
import 'package:ente_logging/logging.dart';
import 'package:ente_network/network.dart';
import "package:ente_strings/l10n/strings_localizations.dart";
import "package:ente_ui/theme/ente_theme_data.dart";
import "package:ente_ui/theme/theme_config.dart";
import 'package:ente_ui/utils/window_listener_service.dart';
import 'package:ente_utils/platform_util.dart';
@@ -103,6 +104,8 @@ Future<void> _runInForeground() async {
lockScreen: LockScreen(Configuration.instance),
enabled: await LockScreenSettings.instance.shouldShowLockScreen(),
locale: locale,
lightTheme: lightThemeData,
darkTheme: darkThemeData,
savedThemeMode: savedThemeMode,
supportedLocales: appSupportedLocales,
localizationsDelegates: const [

View File

@@ -0,0 +1,204 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Philosophy
Ente is focused on privacy, transparency and trust. It's a fully open-source, end-to-end encrypted platform for storing data in the cloud. When contributing, always prioritize:
- User privacy and data security
- End-to-end encryption integrity
- Transparent, auditable code
- Zero-knowledge architecture principles
## Monorepo Context
This is the Ente Photos mobile app within the Ente monorepo. The monorepo contains:
- Mobile apps (Photos, Auth, Locker) at `mobile/apps/`
- Shared packages at `mobile/packages/`
- Web, desktop, CLI, and server components in parent directories
### Package Architecture
The Photos app uses two types of packages:
- **Shared packages** (`../../packages/`): Common code shared across multiple Ente apps (Photos, Auth, Locker)
- **Photos-specific plugins** (`./plugins/`): Custom Flutter plugins specific to Photos app for separation and testability
## Commit & PR Guidelines
⚠️ **CRITICAL: From the default template, use ONLY: Co-Authored-By: Claude <noreply@anthropic.com>** ⚠️
### Pre-commit/PR Checklist (RUN BEFORE EVERY COMMIT OR PR!)
**CRITICAL: CI will fail if ANY of these checks fail. Run ALL commands and ensure they ALL pass.**
```bash
# 1. Analyze flutter code for errors and warnings
flutter analyze
```
**Why CI might fail even after running these:**
- Skipping any command above
- Assuming auto-fix tools handle everything (they don't)
- Not fixing warnings that flutter reports
- Making changes after running the checks
### Commit & PR Message Rules
**These rules apply to BOTH commit messages AND pull request descriptions**
- Keep messages CONCISE (no walls of text)
- Subject line under 72 chars (no body text unless critical)
- NO emojis
- NO promotional text or links (except Co-Authored-By line)
### Additional Guidelines
- Check `git status` before committing to avoid adding temporary/binary files
- Never commit to main branch
- All CI checks must pass - run the checklist commands above before committing or creating PR
## Development Commands
### Using Melos (Monorepo Management)
```bash
# From mobile/ directory - bootstrap all packages
melos bootstrap
# Run Photos app specifically
melos run:photos:apk
# Build Photos APK
melos build:photos:apk
# Clean Photos app
melos clean:photos
```
### Direct Flutter Commands
```bash
# Development run with environment variables
./run.sh # Uses .env file with --flavor dev
# Development run without env file
flutter run -t lib/main.dart --flavor independent
# Build release APK
flutter build apk --release --flavor independent
# iOS build
cd ios && pod install && cd ..
flutter build ios
```
### Code Quality
```bash
# Static analysis and linting
flutter analyze .
# Run tests
flutter test
```
## Architecture Overview
### Service-Oriented Architecture
The app uses a service layer pattern with 28+ specialized services:
- **collections_service.dart**: Album and collection management
- **search_service.dart**: Search functionality with ML support
- **smart_memories_service.dart**: AI-powered memory curation
- **sync_service.dart**: Local/remote synchronization
- **Machine Learning Services**: Face recognition, semantic search, similar images
### Key Patterns
- **Service Locator**: Dependency injection via `lib/service_locator.dart`
- **Event Bus**: Loose coupling via `lib/core/event_bus.dart`
- **Repository Pattern**: Database abstraction in `lib/db/`
- **Rust Integration**: Performance-critical operations via Flutter Rust Bridge
### Security Architecture
- End-to-end encryption with `ente_crypto` package
- BIP39 mnemonic-based key generation (24 words)
- Secure storage using platform-specific implementations
- App lock and privacy screen features
## Project Structure
```
lib/
├── core/ # Configuration, constants, networking
├── services/ # Business logic (28+ services)
├── ui/ # UI components (18 subdirectories)
├── models/ # Data models (17 subdirectories)
├── db/ # SQLite database layer
├── utils/ # Utilities and helpers
├── gateways/ # API gateway interfaces
├── events/ # Event system
├── l10n/ # Localization files (intl_*.arb)
└── generated/ # Auto-generated code including localizations
```
## Localization (Flutter)
- Add new strings to `lib/l10n/intl_en.arb` (English base file)
- Use `AppLocalizations` to access localized strings in code
- Example: `AppLocalizations.of(context).yourStringKey`
- Run code generation after adding new strings: `flutter pub get`
- Translations managed via Crowdin for other languages
## Key Dependencies
- **Flutter 3.32.8** with Dart SDK >=3.3.0 <4.0.0
- **Media**: `photo_manager`, `video_editor`, `ffmpeg_kit_flutter`
- **Storage**: `sqlite_async`, `flutter_secure_storage`
- **ML/AI**: Custom ONNX runtime, `ml_linalg`
- **Rust**: Flutter Rust Bridge for performance
## Development Setup Requirements
1. Install Flutter v3.32.8 and Rust
2. Install Flutter Rust Bridge: `cargo install flutter_rust_bridge_codegen`
3. Generate Rust bindings: `flutter_rust_bridge_codegen generate`
4. Update submodules: `git submodule update --init --recursive`
5. Enable git hooks: `git config core.hooksPath hooks`
## Critical Coding Requirements
### 1. Code Quality - MANDATORY
**Every code change MUST pass `flutter analyze` with zero issues**
- Run `flutter analyze` after EVERY code modification
- Resolve ALL issues (info, warning, error) - no exceptions
- The codebase has zero issues by default, so any issue is from your changes
- DO NOT commit or consider work complete until `flutter analyze` passes cleanly
### 2. Component Reuse - MANDATORY
**Always try to reuse existing components**
- Use a subagent to search for existing components before creating new ones
- Only create new components if none exist that meet the requirements
- Check both UI components in `lib/ui/` and shared components in `../../packages/`
### 3. Design System - MANDATORY
**Never hardcode colors or text styles**
- Always use the Ente design system for colors and typography
- Use a subagent to find the appropriate design tokens
- Access colors via theme: `getEnteColorScheme(context)`
- Access text styles via theme: `getEnteTextTheme(context)`
- Call above theme getters only at the top of (`build`) methods and re-use them throughout the component
- If you MUST use custom colors/styles (extremely rare), explicitly inform the user with a clear warning
### 4. Documentation Sync - MANDATORY
**Keep spec documents synchronized with code changes**
- When modifying code, also update any associated spec documents
- Check for related spec files in `docs/` or project directories
- Ensure documentation reflects the current implementation
- Update examples in specs if behavior changes
## Important Notes
- Large service files (some 70k+ lines) - consider file context when editing
- 400+ dependencies - check existing libraries before adding new ones
- When adding functionality, check both `../../packages/` for shared code and `./plugins/` for Photos-specific plugins
- Performance-critical paths use Rust integration
- Always follow existing code conventions and patterns in neighboring files
# Individual Preferences
- @~/.claude/my-project-instructions.md

View File

@@ -1,7 +1,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="io.ente.photos">
<application android:name="${applicationName}"
<application
tools:replace="android:label"
android:name="${applicationName}"
android:label="@string/app_name"
android:icon="@mipmap/icon_green"
android:usesCleartextTraffic="true"

View File

@@ -1,36 +1,49 @@
Ente is a simple app to backup and share your photos and videos.
Store, share and discover your memories with Ente Photos. With end-to-end encryption, only you—and those you share with—can see your photos and videos. Ente Photos has lovingly protected over 200 million memories for people who trust us across all major platforms. Get started with 10 GB free.
If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
Why Ente Photos?
We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner.
Ente Photos is designed for those who truly value their memories. With end-to-end encryption and secure backups in three locations, your photos stay truly private and safe. Powerful on-device AI helps you find faces and objects instantly, while curated stories bring cherished memories to the present. Share encrypted albums with loved ones, invite family at no extra cost, and lock sensitive images with a password. Available on mobile, desktop, and web, Ente preserves every pixel of your photos and videos.
Ente also makes it simple to share your albums with your loved ones, even if they aren't on Ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app.
Features:
Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
END-TO-END ENCRYPTED STORAGE: Your photos and videos are encrypted on your device, and then automatically backed up to the cloud.
We are here to make the safest photos app ever, come join our journey!
SHARE AND COLLABORATE: Let your family or friends add photos and videos to your albums. Everything, end-to-end encrypted.
FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Collaborative albums, so you can pool together photos after a trip
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password
- Ability to free up space, by removing files that have been safely backed up
- Human support, because you're worth it
- Descriptions, so you can caption your memories and find them easily
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from Google, Apple, your hard drive and more
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
RELIVE YOUR MEMORIES: Through the stories Ente curates for you, relive your memories from previous years. Easily spread the cheer by sharing them with your loved ones or friends.
PERMISSIONS
Ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
SEARCH FOR ANYONE AND ANYTHING: Using on-device AI, Ente helps you find faces and key elements in a photo, so you can search through your entire library using natural language search.
PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
INVITE YOUR FAMILY: Invite up to 5 family members to any paid plan at no extra cost. Only your storage space is shared, not your data. Each member will receive their own private space.
SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
AVAILABLE EVERYWHERE: Ente Photos is available on iOS, Android, Windows, Mac, Linux and the web, so you can access your photos and videos from any device you have.
NEVER LOSE YOUR PHOTOS: Ente stores your encrypted backups in 3 secure locations—including an underground facility—so your photos stay safe, no matter what.
EASY IMPORT: Use our powerful desktop app to import data from other providers. If you need any help moving, reach out, and we'll be there.
ORIGINAL QUALITY BACKUPS: All photos and videos are stored in their original quality, including the metadata, without any compression or loss in quality.
APP LOCK: Make sure no one else can see your photos and videos using the built in App Lock. You can set a pin, or use biometrics to lock the app only for yourself.
HIDDEN PHOTOS: Hide your most private photos and videos to the Hidden folder, which is password protected by default.
FREE DEVICE SPACE: Free up your device's space by clearing files that have already been backed, in a single click.
COLLECT PHOTOS: Went to a party and want to collect all the photos in one place? Just share a link with your friends and ask them to upload.
PARTNER SHARING: Share your camera album with your partner so they can automatically see your photos on their device.
LEGACY: Allow trusted contacts to access your account in your absence.
DARK & LIGHT THEMES: Choose the mode that will make your photos pop.
ADDITIONAL SECURITY: Turn on two-factor authentication or set a lock-screen for the app.
OPEN-SOURCE AND AUDITED: Ente Photoss code is open-source, and has been audited by third-party security experts.
HUMAN SUPPORT: We take pride in providing real human support. If you need help, reach out to support@ente.io, and one of us will be there to assist you.
Keep your memories safe and private, with Ente Photos. Get started with 10 GB free.
Visit ente.io to learn more.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 KiB

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1 +1 @@
Ente Photos is an open source photos app, that provides end-to-end encrypted backups for your photos and videos.
Backup, Organise, Share - Private photo storage with end-to-end encryption

View File

@@ -1 +1 @@
Ente Photos - Open source, end-to-end encrypted alternative to Google Photos
Ente Photos - Encrypted photo storage

View File

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

View File

@@ -1,36 +1,49 @@
ente is a simple app to backup and share your photos and videos.
Store, share and discover your memories with Ente Photos. With end-to-end encryption, only you—and those you share with—can see your photos and videos. Ente Photos has lovingly protected over 165 million memories for people who trust us across all major platforms. Get started with 10 GB free.
If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
Why Ente Photos?
We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner.
Ente Photos is designed for those who truly value their memories. With end-to-end encryption and secure backups in three locations, your photos stay truly private and safe. Powerful on-device AI helps you find faces and objects instantly, while curated stories bring cherished memories to the present. Share encrypted albums with loved ones, invite family at no extra cost, and lock sensitive images with a password. Available on mobile, desktop, and web, Ente preserves every pixel of your photos and videos.
ente also makes it simple to share your albums with your loved ones, even if they aren't on ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app.
Features:
Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
END-TO-END ENCRYPTED STORAGE: Your photos and videos are encrypted on your device, and then automatically backed up to the cloud.
We are here to make the safest photos app ever, come join our journey!
SHARE AND COLLABORATE: Let your family or friends add photos and videos to your albums. Everything, end-to-end encrypted.
FEATURES
- Original quality backups, because every pixel is important
- Family plans, so you can share storage with your family
- Collaborative albums, so you can pool together photos after a trip
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album links, that can be protected with a password
- Ability to free up space, by removing files that have been safely backed up
- Human support, because you're worth it
- Descriptions, so you can caption your memories and find them easily
- Image editor, to add finishing touches
- Favorite, hide and relive your memories, for they are precious
- One-click import from Google, Apple, your hard drive and more
- Dark theme, because your photos look good in it
- 2FA, 3FA, biometric auth
- and a LOT more!
RELIVE YOUR MEMORIES: Through the stories Ente curates for you, relive your memories from previous years. Easily spread the cheer by sharing them with your loved ones or friends.
PERMISSIONS
ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
SEARCH FOR ANYONE AND ANYTHING: Using on-device AI, Ente helps you find faces and key elements in a photo, so you can search through your entire library using natural language search.
PRICING
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
INVITE YOUR FAMILY: Invite up to 5 family members to any paid plan at no extra cost. Only your storage space is shared, not your data. Each member will receive their own private space.
SUPPORT
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
AVAILABLE EVERYWHERE: Ente Photos is available on iOS, Android, Windows, Mac, Linux and the web, so you can access your photos and videos from any device you have.
NEVER LOSE YOUR PHOTOS: Ente stores your encrypted backups in 3 secure locations—including an underground facility—so your photos stay safe, no matter what.
EASY IMPORT: Use our powerful desktop app to import data from other providers. If you need any help moving, reach out, and we'll be there.
ORIGINAL QUALITY BACKUPS: All photos and videos are stored in their original quality, including the metadata, without any compression or loss in quality.
APP LOCK: Make sure no one else can see your photos and videos using the built in App Lock. You can set a pin, or use biometrics to lock the app only for yourself.
HIDDEN PHOTOS: Hide your most private photos and videos to the Hidden folder, which is password protected by default.
FREE DEVICE SPACE: Free up your device's space by clearing files that have already been backed, in a single click.
COLLECT PHOTOS: Went to a party and want to collect all the photos in one place? Just share a link with your friends and ask them to upload.
PARTNER SHARING: Share your camera album with your partner so they can automatically see your photos on their device.
LEGACY: Allow trusted contacts to access your account in your absence.
DARK & LIGHT THEMES: Choose the mode that will make your photos pop.
ADDITIONAL SECURITY: Turn on two-factor authentication or set a lock-screen for the app.
OPEN-SOURCE AND AUDITED: Ente Photoss code is open-source, and has been audited by third-party security experts.
HUMAN SUPPORT: We take pride in providing real human support. If you need help, reach out to support@ente.io, and one of us will be there to assist you.
Keep your memories safe and private, with Ente Photos. Get started with 10 GB free.
Visit ente.io to learn more.

View File

@@ -1 +1 @@
ente is an end-to-end encrypted photo storage app
Backup, Organise, Share - Private photo storage with end-to-end encryption

View File

@@ -1 +1 @@
ente - encrypted photo storage
Ente Photos - Encrypted photo storage

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 584 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

After

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 KiB

After

Width:  |  Height:  |  Size: 521 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -1,4 +1,4 @@
ente es una aplicación simple para hacer copias de seguridad y compartir tus fotos y videos.
ente es una aplicación simple para hacer copias de seguridad y compartir tus fotos y vídeos.
Si has estado buscando una alternativa a Google Photos que sea amigable con la privacidad, has llegado al lugar correcto. Con Ente, se almacenan cifradas de extremo a extremo (e2ee). Esto significa que solo tú puedes verlas.

View File

@@ -6,23 +6,23 @@ Kami menyediakan app untuk Android, iOS, web, serta desktop, dan fotomu akan ter
ente juga dapat memudahkan kamu untuk membagikan album ke orang tersayang, meski mereka tidak punya akun ente. Kamu dapat membagikan link berbagi publik, di mana mereka bisa melihat album kamu dan berkolaborasi dengan menambahkan foto, tanpa akun atau app.
Data terenkripsi kamu tersimpan di 3 lokasi berbeda, termasuk di salah satu tempat pengungsian di Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
Data terenkripsi kamu tersimpan di 3 lokasi berbeda, termasuk di salah satu tempat pengungsian di Paris. Kami menanggapi keturunan anda dengan serius dan memudahkan untuk memastikan kenangan anda tetap ada setelah anda.
Kami ingin membuat app foto yang paling aman sepanjang masajadi, bergabunglah dengan kami!
FITUR
- Pencadangan kualitas asli, karena setiap piksel berarti
- Paket keluarga, sehingga kamu bisa bagikan kuota penyimpananmu dengan keluarga
- Collaborative albums, so you can pool together photos after a trip
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Album kolaboratif, sehingga anda dapat mengumpulkan foto bersama setelah sebuah perjalanan
- Folder bersama, jika anda ingin pasangan anda menikmati hasil jepretan "Kamera" anda
- Link album, yang bisa dilindungi dengan sandi
Kemampuan untuk membebaskan kapasitas, dengan menghilangkan files yang sudah di back-up dengan aman
- Human support, because you're worth it
- Descriptions, so you can caption your memories and find them easily
- Dukungan manusia, karena anda layak mendapatkannya
- Deskripsi, sehingga anda dapat memberi keterangan pada memori anda dan menemukannya dengan mudah
- Editor gambar, untuk menyempurnakan fotomu
- Favorite, hide and relive your memories, for they are precious
- Favoritkan, sembunyikan, dan kenang kembali memori anda, karena itu sangat berharga
- Pengimporan mudah dari Google, Apple, hard drive-mu, dan lainnya
- Dark theme, because your photos look good in it
- Tema gelap, karena foto anda terlihat bagus di dalamnya
- Autentikasi dua atau tiga faktor dan autentikasi biometrik
- dan BANYAK LAGI!

View File

@@ -6,20 +6,20 @@ Kami menyediakan app untuk semua platform, dan fotomu akan tersinkron di semua p
Ente juga memudahkan kamu untuk membagikan album ke kerabatmu. Kamu bisa membagikannya secara langsung ke pengguna Ente lain, terenkripsi ujung ke ujung; atau dengan link yang dapat dilihat publik.
Data terenkripsi kamu tersimpan di berbagai lokasi, termasuk di salah satu tempat pengungsian di Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
Data terenkripsi kamu tersimpan di berbagai lokasi, termasuk di salah satu tempat pengungsian di Paris. Kami menanggapi keturunan anda dengan serius dan memudahkan untuk memastikan kenangan anda tetap ada setelah anda.
Kami ingin membuat app foto yang paling aman sepanjang masajadi, bergabunglah dengan kami!
FITUR
- Pencadangan kualitas asli, karena setiap piksel berarti
- Paket keluarga, sehingga kamu bisa bagikan kuota penyimpananmu dengan keluarga
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Folder bersama, jika anda ingin pasangan anda menikmati hasil jepretan "Kamera" anda
- Link album, yang bisa dilindungi dengan sandi dan diatur waktu kedaluwarsanya
- Ability to free up space, by removing files that have been safely backed up
- Kemampuan untuk mengosongkan ruang, dengan menghapus file yang telah dicadangkan dengan aman
- Editor gambar, untuk menyempurnakan fotomu
- Favorite, hide and relive your memories, for they are precious
- One-click import from all major storage providers
- Dark theme, because your photos look good in it
Favoritkan, sembunyikan, dan kenang kembali memori anda, karena itu sangat berharga
Impor sekali klik dari semua penyedia penyimpanan utama
Tema gelap, karena foto anda terlihat bagus di dalamnya
- Autentikasi dua atau tiga faktor dan autentikasi biometrik
- dan BANYAK LAGI!
@@ -27,7 +27,7 @@ HARGA
Kami tidak menyediakan paket yang gratis seumur hidup, karena penting bagi kami untuk tetap berdiri dan bertahan hingga masa depan. Namun, kami menyediakan paket yang terjangkau, yang bisa kamu bagikan dengan keluargamu. Kamu bisa menemukan informasi lebih lanjut di ente.io.
DUKUNGAN
We take pride in offering human support. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam.
Kami bangga dapat menawarkan dukungan manusia. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam.
KETENTUAN
https://ente.io/terms

View File

@@ -6,20 +6,20 @@ Kami menyediakan app untuk Android, iOS, Web, serta Desktop, dan fotomu akan ter
Ente juga memudahkan kamu untuk membagikan album ke kerabatmu. Kamu bisa membagikannya secara langsung ke pengguna Ente lain, terenkripsi ujung ke ujung; atau dengan link yang dapat dilihat publik.
Data terenkripsi kamu tersimpan di berbagai lokasi, termasuk di salah satu tempat pengungsian di Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
Data terenkripsi kamu tersimpan di berbagai lokasi, termasuk di salah satu tempat pengungsian di Paris. Kami menanggapi keturunan anda dengan serius dan memudahkan untuk memastikan kenangan anda tetap ada setelah anda.
Kami ingin membuat app foto yang paling aman sepanjang masajadi, bergabunglah dengan kami!
✨ FITUR
- Pencadangan kualitas asli, karena setiap piksel berarti
- Paket keluarga, sehingga kamu bisa bagikan kuota penyimpananmu dengan keluarga
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
- Folder bersama, jika anda ingin pasangan anda menikmati hasil jepretan "Kamera" anda
- Link album, yang bisa dilindungi dengan sandi dan diatur waktu kedaluwarsanya
- Ability to free up space, by removing files that have been safely backed up
- Kemampuan untuk mengosongkan ruang, dengan menghapus file yang telah dicadangkan dengan aman
- Editor gambar, untuk menyempurnakan fotomu
- Favorite, hide and relive your memories, for they are precious
- Favoritkan, sembunyikan, dan kenang kembali memori anda, karena itu sangat berharga
- Pengimporan mudah dari Google, Apple, hard drive-mu, dan lainnya
- Dark theme, because your photos look good in it
- Tema gelap, karena foto anda terlihat bagus di dalamnya
- Autentikasi dua atau tiga faktor dan autentikasi biometrik
- dan BANYAK LAGI!
@@ -27,4 +27,4 @@ Kami ingin membuat app foto yang paling aman sepanjang masajadi, bergabunglah
Kami tidak menyediakan paket yang gratis seumur hidup, karena penting bagi kami untuk tetap berdiri dan bertahan hingga masa depan. Namun, kami menyediakan paket yang terjangkau, yang bisa kamu bagikan dengan keluargamu. Kamu bisa menemukan informasi lebih lanjut di ente.io.
🙋 DUKUNGAN
We take pride in offering human support. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam.
Kami bangga dapat menawarkan dukungan manusia. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam.

View File

@@ -181,6 +181,8 @@ PODS:
- PromisesObjC (2.4.0)
- receive_sharing_intent (1.8.1):
- Flutter
- rive_common (0.0.1):
- Flutter
- rust_lib_photos (0.0.1):
- Flutter
- SDWebImage (5.21.1):
@@ -291,6 +293,7 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
- rive_common (from `.symlinks/plugins/rive_common/ios`)
- rust_lib_photos (from `.symlinks/plugins/rust_lib_photos/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
@@ -309,7 +312,7 @@ DEPENDENCIES:
- workmanager (from `.symlinks/plugins/workmanager/ios`)
SPEC REPOS:
https://github.com/ente-io/ffmpeg-kit-custom-repo-ios:
https://github.com/ente-io/ffmpeg-kit-custom-repo-ios.git:
- ffmpeg_kit_custom
trunk:
- Firebase
@@ -418,6 +421,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/privacy_screen/ios"
receive_sharing_intent:
:path: ".symlinks/plugins/receive_sharing_intent/ios"
rive_common:
:path: ".symlinks/plugins/rive_common/ios"
rust_lib_photos:
:path: ".symlinks/plugins/rust_lib_photos/ios"
sentry_flutter:
@@ -452,84 +457,85 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/workmanager/ios"
SPEC CHECKSUMS:
app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7
battery_info: 83f3aae7be2fccefab1d2bf06b8aa96f11c8bcdd
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
emoji_picker_flutter: ed468d9746c21711e66b2788880519a9de5de211
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
battery_info: b6c551049266af31556b93c9d9b9452cfec0219f
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
emoji_picker_flutter: fe2e6151c5b548e975d546e6eeb567daf0962a58
ffmpeg_kit_custom: 682b4f2f1ff1f8abae5a92f6c3540f2441d5be99
ffmpeg_kit_flutter: 915b345acc97d4142e8a9a8549d177ff10f043f5
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
ffmpeg_kit_flutter: 9dce4803991478c78c6fb9f972703301101095fe
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
firebase_core: ece862f94b2bc72ee0edbeec7ab5c7cb09fe1ab5
firebase_messaging: e1a5fae495603115be1d0183bc849da748734e2b
firebase_core: cf4d42a8ac915e51c0c2dc103442f3036d941a2d
firebase_messaging: fee490327c1aae28a0da1e65fca856547deca493
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: aa1e9772696691d02cd91fea829856c11efb8e58
flutter_image_compress_common: 1697a328fd72bfb335507c6bca1a65fa5ad87df1
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_sodium: 7e4621538491834eba53bd524547854bcbbd6987
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa
flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
flutter_sodium: a00383520fc689c688b66fd3092984174712493e
flutter_timezone: ac3da59ac941ff1c98a2e1f0293420e020120282
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
in_app_purchase_storekit: d1a48cb0f8b29dbf5f85f782f5dd79b21b90a5e6
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
launcher_icon_switcher: 84c218d233505aa7d8655d8fa61a3ba802c022da
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
in_app_purchase_storekit: a1ce04056e23eecc666b086040239da7619cd783
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
launcher_icon_switcher: 8e0ad2131a20c51c1dd939896ee32e70cd845b37
libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8
local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
media_extension: 671e2567880d96c95c65c9a82ccceed8f2e309fd
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1
motionphoto: 23e2aeb5c6380112f69468d71f970fa7438e5ed1
move_to_background: 7e3467dd2a1d1013e98c9c1cb93fd53cd7ef9d84
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
media_extension: 6618f07abd762cdbfaadf1b0c56a287e820f0c84
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
motionphoto: 8b65ce50c7d7ff3c767534fc3768b2eed9ac24e4
move_to_background: cd3091014529ec7829e342ad2d75c0a11f4378a5
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: 6809dec117e8997161dbfb42a6f90d6df71a504d
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
native_video_player: 29ab24a926804ac8c4a57eb6d744c7d927c2bc3e
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
open_mail_app: 7314a609e88eed22d53671279e189af7a0ab0f11
open_mail_app: 70273c53f768beefdafbe310c3d9086e4da3cb02
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: 1d80ae07a89a67dfbcae95953a1e5a24af7c3e62
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
photo_manager: 81954a1bf804b6e882d0453b3b6bc7fad7b47d3d
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
rust_lib_photos: 04d3901908d2972192944083310b65abf410774c
receive_sharing_intent: 79c848f5b045674ad60b9fea3bafea59962ad2c1
rive_common: 4743dbfd2911c99066547a3c6454681e0fa907df
rust_lib_photos: 8813b31af48ff02ca75520cbc81a363a13d51a84
SDWebImage: f29024626962457f3470184232766516dee8dfea
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
sqlite3: 1d85290c3321153511f6e900ede7a1608718bbd5
sqlite3_flutter_libs: e7fc8c9ea2200ff3271f08f127842131746b70e2
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
thermal: d4c48be750d1ddbab36b0e2dcb2471531bc8df41
ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
vibration: 8e2f50fc35bb736f9eecb7dd9f7047fbb6a6e888
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
volume_controller: 3657a1f65bedb98fa41ff7dc5793537919f31b12
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
workmanager: b89e4e4445d8b57ee2fdbf1c3925696ebe5b8990
sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
thermal: a9261044101ae8f532fa29cab4e8270b51b3f55c
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
vibration: 7d883d141656a1c1a6d8d238616b2042a51a1241
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
volume_controller: 2e3de73d6e7e81a0067310d17fb70f2f86d71ac7
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
workmanager: 05afacf221f5086e18450250dce57f59bb23e6b0
PODFILE CHECKSUM: cce2cd3351d3488dca65b151118552b680e23635

View File

@@ -565,6 +565,7 @@
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
"${BUILT_PRODUCTS_DIR}/rive_common/rive_common.framework",
"${BUILT_PRODUCTS_DIR}/rust_lib_photos/rust_lib_photos.framework",
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
@@ -662,6 +663,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rive_common.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rust_lib_photos.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",

View File

@@ -142,7 +142,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
debugShowCheckedModeBanner: false,
builder: EasyLoading.init(),
locale: locale,
supportedLocales: AppLocalizations.supportedLocales,
supportedLocales: appSupportedLocales,
localeListResolutionCallback: localResolutionCallBack,
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,
@@ -164,7 +164,7 @@ class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
debugShowCheckedModeBanner: false,
builder: EasyLoading.init(),
locale: locale,
supportedLocales: AppLocalizations.supportedLocales,
supportedLocales: appSupportedLocales,
localeListResolutionCallback: localResolutionCallBack,
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,

View File

@@ -28,6 +28,7 @@ import 'package:photos/services/favorites_service.dart';
import "package:photos/services/home_widget_service.dart";
import 'package:photos/services/ignored_files_service.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import "package:photos/services/machine_learning/similar_images_service.dart";
import 'package:photos/services/search_service.dart';
import 'package:photos/services/sync/sync_service.dart';
import 'package:photos/utils/file_uploader.dart';
@@ -196,6 +197,7 @@ class Configuration {
await CollectionsDB.instance.clearTable();
await MemoriesDB.instance.clearTable();
await MLDataDB.instance.clearTable();
await SimilarImagesService.instance.clearCache();
await UploadLocksDB.instance.clearTable();
await IgnoredFilesService.instance.reset();

View File

@@ -0,0 +1,13 @@
// Common runtime exceptions that can occur during normal app operation.
// These are recoverable conditions that should be caught and handled.
class WidgetUnmountedException implements Exception {
final String? message;
WidgetUnmountedException([this.message]);
@override
String toString() => message != null
? 'WidgetUnmountedException: $message'
: 'WidgetUnmountedException';
}

View File

@@ -39,10 +39,26 @@ class ClipVectorDB {
final documentsDirectory = await getApplicationDocumentsDirectory();
final String dbPath = join(documentsDirectory.path, _databaseName);
_logger.info("Opening vectorDB access: DB path " + dbPath);
final vectorDB = VectorDb(
filePath: dbPath,
dimensions: _embeddingDimension,
);
late VectorDb vectorDB;
try {
vectorDB = VectorDb(
filePath: dbPath,
dimensions: _embeddingDimension,
);
} catch (e, s) {
_logger.severe("Could not open VectorDB at path $dbPath", e, s);
_logger.severe("Deleting the index file and trying again");
await deleteIndexFile();
try {
vectorDB = VectorDb(
filePath: dbPath,
dimensions: _embeddingDimension,
);
} catch (e, s) {
_logger.severe("Still can't open VectorDB at path $dbPath", e, s);
rethrow;
}
}
final stats = await getIndexStats(vectorDB);
_logger.info("VectorDB connection opened with stats: ${stats.toString()}");
@@ -279,17 +295,23 @@ class ClipVectorDB {
}
}
Future<void> deleteIndexFile() async {
Future<void> deleteIndexFile({bool undoMigration = false}) async {
try {
final documentsDirectory = await getApplicationDocumentsDirectory();
final String dbPath =
join(documentsDirectory.path, _databaseName);
final String dbPath = join(documentsDirectory.path, _databaseName);
_logger.info("Delete index file: DB path " + dbPath);
final file = File(dbPath);
if (await file.exists()) {
await file.delete();
}
_logger.info("Deleted index file on disk");
_vectorDbFuture = null;
if (undoMigration) {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_kMigrationKey, false);
_migrationDone = false;
_logger.info("Undid migration flag");
}
} catch (e, s) {
_logger.severe("Error deleting index file on disk", e, s);
rethrow;

View File

@@ -1292,8 +1292,11 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
int processedCount = 0;
int weirdCount = 0;
int whileCount = 0;
const String migrationKey = "clip_vector_db_migration_in_progress";
final stopwatch = Stopwatch()..start();
try {
// Make sure no other heavy compute is running
computeController.blockCompute(blocker: migrationKey);
while (true) {
whileCount++;
_logger.info("$whileCount st round of while loop");
@@ -1323,6 +1326,9 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
embeddings.add(Float32List.view(result[embeddingColumn].buffer));
} else {
weirdCount++;
_logger.warning(
"Weird clip embedding length ${embedding.length} for fileID ${result[fileIDColumn]}, skipping",
);
}
}
_logger.info(
@@ -1349,7 +1355,7 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
"migrated all $totalCount embeddings to ClipVectorDB in ${stopwatch.elapsed.inMilliseconds} ms, with $weirdCount weird embeddings not migrated",
);
await ClipVectorDB.instance.setMigrationDone();
_logger.info("ClipVectorDB migration done, flag file created");
_logger.info("ClipVectorDB migration done");
} catch (e, s) {
_logger.severe(
"Error migrating ClipVectorDB after ${stopwatch.elapsed.inMilliseconds} ms, clearing out DB again",
@@ -1360,6 +1366,8 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
rethrow;
} finally {
stopwatch.stop();
// Make sure compute can run again
computeController.unblockCompute(blocker: migrationKey);
}
}

View File

@@ -1776,11 +1776,6 @@
"same": "نفس",
"different": "مختلف",
"sameperson": "نفس الشخص؟",
"cLTitle1": "محرر الصور المتقدم",
"cLDesc1": "نحن بصدد إطلاق محرر صور جديد ومتقدم يضيف المزيد من إطارات الاقتصاص، والإعدادات المسبقة للفلاتر من أجل تعديلات سريعة، وخيارات الضبط الدقيق التي تشمل التشبع، والتباين، والسطوع، ودرجة الحرارة، وغير ذلك الكثير. يتضمن المحرر الجديد أيضا القدرة على الرسم على صورك وإضافة الرموز التعبيرية كملصقات.",
"cLTitle2": "ألبومات ذكية",
"cLTitle3": "معرض محسن",
"cLTitle4": "تمرير أسرع",
"thisWeek": "هذا الأسبوع",
"lastWeek": "الأسبوع الماضي",
"thisMonth": "هذا الشهر",
@@ -1821,4 +1816,4 @@
}
}
}
}
}

View File

@@ -314,7 +314,7 @@
}
}
},
"faq": "Často kladené dotazy",
"faq": "Často kladené dotazy (FAQ)",
"help": "Nápověda",
"oopsSomethingWentWrong": "Jejda, něco se pokazilo",
"peopleUsingYourCode": "Lidé, kteří používají váš kód",
@@ -498,7 +498,7 @@
"authToChangeYourEmail": "Pro změnu e-mailové adresy se prosím ověřte",
"changePassword": "Změnit heslo",
"authToChangeYourPassword": "Pro změnu hesla se prosím ověřte",
"emailVerificationToggle": "Ověření emailem",
"emailVerificationToggle": "Ověření pomocí e-mailu",
"authToChangeEmailVerificationSetting": "Pro změnu ověření pomocí emailu se musíte ověřit",
"exportYourData": "Exportujte svá data",
"logout": "Odhlásit se",
@@ -594,7 +594,7 @@
"theme": "Motiv",
"lightTheme": "Světlý",
"darkTheme": "Tmavý",
"systemTheme": "Podle systému",
"systemTheme": "Systém",
"freeTrial": "Bezplatná zkušební verze",
"selectYourPlan": "Vyberte svůj tarif",
"enteSubscriptionPitch": "Ente uchovává vaše vzpomínky, takže jsou vám vždy k dispozici, i když ztratíte své zařízení.",
@@ -1776,14 +1776,6 @@
"same": "Stejné",
"different": "Odlišné",
"sameperson": "Stejná osoba?",
"cLTitle1": "Pokročilý editor obrázků",
"cLDesc1": "Vydáváme nový a pokročilý editor obrázků, který přidává více ořezových rámečků, přednastavené filtry pro rychlé úpravy, možnosti jemného doladění včetně sytosti, kontrastu, jasu, teploty a mnoho dalšího. Nový editor také zahrnuje možnost kreslit na vaše fotografie a přidávat emodži jako nálepky.",
"cLTitle2": "Chytrá alba",
"cLDesc2": "Nyní můžete automaticky přidávat fotografie vybraných osob do libovolného alba. Stačí přejít do alba a v rozbalovací nabídce vybrat možnost „Automaticky přidat osoby“. Pokud tuto funkci použijete společně se sdíleným albem, můžete sdílet fotografie bez jediného kliknutí.",
"cLTitle3": "Vylepšená galerie",
"cLDesc3": "Přidali jsme možnost seskupit vaši galerii podle týdnů, měsíců a let. Nyní můžete svou galerii přizpůsobit tak, aby vypadala přesně podle vašich představ, a to díky těmto novým možnostem seskupování a přizpůsobitelným mřížkám",
"cLTitle4": "Rychlejší posouvání",
"cLDesc4": "Kromě řady vylepšení pod kapotou, která zlepšují procházení galerií, jsme také přepracovali posuvník tak, aby zobrazoval značky, díky nimž můžete rychle přeskakovat po časové ose.",
"indexingPausedStatusDescription": "Indexování je pozastaveno. Automaticky se obnoví, jakmile bude zařízení připraveno. Zařízení je považováno za připravené, pokud jsou úroveň nabití baterie, stav baterie a teplotní stav v normálním rozmezí.",
"thisWeek": "Tento týden",
"lastWeek": "Minulý týden",
@@ -1827,5 +1819,98 @@
"type": "int"
}
}
}
}
},
"videosProcessed": "Zpracovaná videa",
"totalVideos": "Celkový počet videí",
"skippedVideos": "Přeskočená videa",
"videoStreamingNote": "Na tomto zařízení se zpracovávají pouze videa z posledních 60 dnů, která jsou kratší než 1 minuta. U starších/delších videí povolte streamování v desktopové aplikaci.",
"createStream": "Vytvořit stream",
"recreateStream": "Obnovit stream",
"addedToStreamCreationQueue": "Přidáno do fronty pro vytvoření streamu",
"addedToStreamRecreationQueue": "Přidáno do fronty pro obnovení streamu",
"videoPreviewAlreadyExists": "Náhled videa již existuje",
"videoAlreadyInQueue": "Video soubor již je ve frontě",
"addedToQueue": "Přidáno do fronty",
"creatingStream": "Vytváření streamu",
"similarImages": "Podobné obrázky",
"findSimilarImages": "Najít podobné obrázky",
"noSimilarImagesFound": "Nebyly nalezeny žádné podobné obrázky",
"yourPhotosLookUnique": "Vaše fotografie vypadají jedinečně",
"similarGroupsFound": "{count, plural, =1{{count} skupina nalezena} few{{count} skupiny nalezeny} other{{count} skupin nalezeno}}",
"@similarGroupsFound": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"reviewAndRemoveSimilarImages": "Zkontrolujte a odstraňte podobné obrázky",
"deletePhotosWithSize": "Smazat {count} fotek ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "Možnosti výběru",
"selectExactWithCount": "Úplně stejné ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "Vybrat shodné",
"selectSimilarWithCount": "Hodně podobné ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "Vyberte podobné",
"selectAllWithCount": "Všechny podobnosti ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "Vyberte podobné obrázky",
"chooseSimilarImagesToSelect": "Vyberte obrázky na základě jejich vizuální podobnosti",
"clearSelection": "Vymazat výběr",
"similarImagesCount": "{count} podobných obrázků",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "Smazat ({count})",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "Smazat soubory",
"areYouSureDeleteFiles": "Opravdu chcete tyto soubory smazat?",
"greatJob": "Dobrá práce!",
"size": "Velikost",
"similarity": "Podobnost",
"processingLocally": "Místní zpracování",
"useMLToFindSimilarImages": "Zkontrolujte a odstraňte obrázky, které se navzájem podobají.",
"all": "Vše",
"similar": "Podobné",
"identical": "Identické",
"nothingHereTryAnotherFilter": "Tady nic není, zkuste jiný filtr! 👀"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Gleich",
"different": "Verschieden",
"sameperson": "Dieselbe Person?",
"cLTitle1": "Erweiterte Bildbearbeitung",
"cLDesc1": "Wir veröffentlichen eine neue und erweiterte Bildbearbeitung, die mehr Bildzuschnitte ermöglicht, vordefinierte Filter für schnelleres Bearbeiten bietet, sowie die Feinabstimmung von Sättigung, Kontrast, Helligkeit und vielem mehr erlaubt. Der neue Editor erlaubt außerdem das Zeichnen auf den Fotos oder das Hinzufügen von Emojis als Sticker.",
"cLTitle2": "Intelligente Alben",
"cLDesc2": "Du kannst jetzt automatisch Fotos von ausgewählten Personen zu jedem Album hinzufügen. Öffne einfach das Album und wähle \"Personen automatisch hinzufügen\" aus dem Menü. Zusammen mit einem geteilten Album kannst Du Fotos mit null Klicks teilen.",
"cLTitle3": "Verbesserte Galerie",
"cLDesc3": "Wir haben die Möglichkeit hinzugefügt, Alben nach Wochen, Monaten und Jahren zu gruppieren. Du kannst jetzt die Galerie mit diesen neuen Gruppierungs-Optionen so anpassen, dass sie genau so aussieht, wie Du möchtest, zusammen mit angepassten Rastern",
"cLTitle4": "Schnelleres Scrollen",
"cLDesc4": "Zusammen mit einem Schwung Änderungen unter der Haube, um das Erlebnis beim Scrollen der Galerie zu verbessern, haben wir außerdem den Scrollbalken mit Markern neu gestaltet, um es Dir zu ermöglichen, schnell in der Zeitleiste zu springen.",
"indexingPausedStatusDescription": "Die Indizierung ist pausiert. Sie wird automatisch fortgesetzt, wenn das Gerät bereit ist. Das Gerät wird als bereit angesehen, wenn sich der Akkustand, die Akkugesundheit und der thermische Zustand in einem gesunden Bereich befinden.",
"thisWeek": "Diese Woche",
"lastWeek": "Letzte Woche",
@@ -1827,5 +1819,117 @@
"type": "int"
}
}
}
},
"videosProcessed": "Videos verarbeitet",
"totalVideos": "Videos insgesamt",
"skippedVideos": "Übersprungene Videos",
"videoStreamingDescriptionLine1": "Videos sofort auf jedem Gerät abspielen.",
"videoStreamingDescriptionLine2": "Aktivieren, um Video-Streams auf diesem Gerät zu verarbeiten.",
"videoStreamingNote": "Nur Videos der letzten 60 Tage und unter einer Minute werden auf diesem Gerät verarbeitet. Für ältere/längere Videos aktiviere das Streaming in der Desktop-App.",
"createStream": "Stream erzeugen",
"recreateStream": "Stream neu erzeugen",
"addedToStreamCreationQueue": "Zur Warteschlange für Streamerstellung hinzugefügt",
"addedToStreamRecreationQueue": "Zur Warteschlange für Neuerstellung der Streams hinzugefügt",
"videoPreviewAlreadyExists": "Videovorschau existiert bereits",
"videoAlreadyInQueue": "Videodatei existiert bereits in der Warteschlange",
"addedToQueue": "Zur Warteschlange hinzugefügt",
"creatingStream": "Stream wird erzeugt",
"similarImages": "Ähnliche Bilder",
"findSimilarImages": "Ähnliche Bilder finden",
"noSimilarImagesFound": "Keine ähnlichen Bilder gefunden",
"yourPhotosLookUnique": "Deine Fotos sehen einzigartig aus",
"similarGroupsFound": "{count, plural, =1{Eine Gruppe gefunden} other{{count} Gruppen gefunden}}",
"@similarGroupsFound": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"reviewAndRemoveSimilarImages": "Überprüfe und lösche ähnliche Bilder",
"deletePhotosWithSize": "Lösche {count} Fotos ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "Auswahloptionen",
"selectExactWithCount": "Exakt gleich ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "Exakte auswählen",
"selectSimilarWithCount": "Nahezu gleich ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "Ähnliche auswählen",
"selectAllWithCount": "Alle Ähnlichkeiten ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "Ähnliche Bilder auswählen",
"chooseSimilarImagesToSelect": "Wähle Bilder anhand ihrer visuellen Ähnlichkeit",
"clearSelection": "Auswahl aufheben",
"similarImagesCount": "{count} ähnliche Bilder",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "Löschen ({count})",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "Dateien löschen",
"areYouSureDeleteFiles": "Bist du sicher, dass du diese Dateien löschen willst?",
"greatJob": "Gut gemacht!",
"cleanedUpSimilarImages": "Du hast {size} an Speicherplatz freigegeben",
"@cleanedUpSimilarImages": {
"placeholders": {
"size": {
"type": "String"
}
}
},
"size": "Größe",
"similarity": "Ähnlichkeit",
"processingLocally": "Lokale Verarbeitung",
"useMLToFindSimilarImages": "Überprüfe und entferne Bilder, die sich ähnlich sehen.",
"all": "Alle",
"similar": "Ähnlich",
"identical": "Identisch",
"nothingHereTryAnotherFilter": "Nichts zu sehen, probier einen anderen Filter! 👀",
"related": "Verwandt",
"hoorayyyy": "Hurraaaa!",
"nothingToTidyUpHere": "Hier gibt es nichts zu bereinigen",
"cLTitle1": "Ähnliche Bilder",
"cLDesc1": "Wir führen ein neues ML-basiertes System ein, um ähnliche Bilder zu erkennen, mit dem du deine Bibliothek bereinigen kannst. Verfügbar unter Einstellungen -> Sicherung -> Speicherplatz freigeben",
"cLTitle2": "Video-Streaming-Verbesserungen",
"cLDesc2": "Du kannst jetzt die Stream-Generierung für Videos direkt aus der App manuell auslösen. Wir haben auch einen neuen Video-Streaming-Einstellungsbildschirm hinzugefügt, der dir zeigt, welcher Prozentsatz deiner Videos für das Streaming verarbeitet wurde",
"cLTitle3": "Leistungsverbesserungen",
"cLDesc3": "Mehrere Verbesserungen im Hintergrund, einschließlich besserer Cache-Nutzung und einer flüssigeren Scroll-Erfahrung"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Same",
"different": "Different",
"sameperson": "Same person?",
"cLTitle1": "Advanced Image Editor",
"cLDesc1": "We are releasing a new and advanced image editor that add more cropping frames, filter presets for quick edits, fine tuning options including saturation, contrast, brightness, temperature and a lot more. The new editor also includes the ability to draw on your photos and add emojis as stickers.",
"cLTitle2": "Smart Albums",
"cLDesc2": "You can now automatically add photos of selected people to any album. Just go the album, and select \"auto-add people\" from the overflow menu. If used along with shared album, you can share photos with zero clicks.",
"cLTitle3": "Improved Gallery",
"cLDesc3": "We have added the ability to group your gallery by weeks, months, and years. You can now customise your gallery to look exactly the way you want with these new grouping options, along with custom grids",
"cLTitle4": "Faster Scroll",
"cLDesc4": "Along with a bunch of under the hood improvements to improve the gallery scroll experience, we have also redesigned the scroll bar to show markers, allowing you to quickly jump across the timeline.",
"indexingPausedStatusDescription": "Indexing is paused. It will automatically resume when the device is ready. The device is considered ready when its battery level, battery health, and thermal status are within a healthy range.",
"thisWeek": "This week",
"lastWeek": "Last week",
@@ -1843,14 +1835,6 @@
"addedToQueue": "Added to queue",
"creatingStream": "Creating stream",
"similarImages": "Similar images",
"deletingProgress": "Deleting... {progress}",
"@deletingProgress": {
"placeholders": {
"progress": {
"type": "String"
}
}
},
"findSimilarImages": "Find similar images",
"noSimilarImagesFound": "No similar images found",
"yourPhotosLookUnique": "Your photos look unique",
@@ -1933,11 +1917,11 @@
},
"size": "Size",
"similarity": "Similarity",
"analyzingPhotosLocally": "Analyzing your photos locally",
"lookingForVisualSimilarities": "Looking for visual similarities",
"comparingImageDetails": "Comparing image details",
"findingSimilarImages": "Finding similar images",
"almostDone": "Almost done",
"analyzingPhotosLocally": "Analyzing your photos locally...",
"lookingForVisualSimilarities": "Looking for visual similarities...",
"comparingImageDetails": "Comparing image details...",
"findingSimilarImages": "Finding similar images...",
"almostDone": "Almost done...",
"processingLocally": "Processing locally",
"useMLToFindSimilarImages": "Review and remove images that look similar to each other.",
"all": "All",
@@ -1946,5 +1930,12 @@
"nothingHereTryAnotherFilter": "Nothing here, try another filter! 👀",
"related": "Related",
"hoorayyyy": "Hoorayyyy!",
"nothingToTidyUpHere": "Nothing to tidy up here"
"nothingToTidyUpHere": "Nothing to tidy up here",
"deletingDash": "Deleting - ",
"cLTitle1": "Similar images",
"cLDesc1": "We are introducing a new ML-based system to detect similar images, using which you can cleanup your library. Available in Settings -> Backup -> Free up space",
"cLTitle2": "Video streaming enhancements",
"cLDesc2": "You can now manually trigger stream generation for videos directly from the app. We have also added a new video streaming settings screen which will show you what percentage of your videos have been processed for streaming",
"cLTitle3": "Performance improvements",
"cLDesc3": "Multiple under the hood improvements, including better cache usage and a smoother scroll experience"
}

View File

@@ -159,7 +159,7 @@
"addCollaborator": "Agregar colaborador",
"addANewEmail": "Agregar nuevo correo electrónico",
"orPickAnExistingOne": "O elige uno existente",
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Colaboradores pueden añadir fotos y videos al álbum compartido.",
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Colaboradores pueden añadir fotos y vídeos al álbum compartido.",
"enterEmail": "Ingresar correo electrónico ",
"albumOwner": "Propietario",
"@albumOwner": {
@@ -270,7 +270,7 @@
"shareTextConfirmOthersVerificationID": "Hola, ¿puedes confirmar que esta es tu ID de verificación ente.io: {verificationID}?",
"somethingWentWrong": "Algo salió mal",
"sendInvite": "Enviar invitación",
"shareTextRecommendUsingEnte": "Descarga Ente para que podamos compartir fácilmente fotos y videos en calidad original.\n\nhttps://ente.io",
"shareTextRecommendUsingEnte": "Descarga Ente para que podamos compartir fácilmente fotos y vídeos en calidad original.\n\nhttps://ente.io",
"done": "Hecho",
"applyCodeTitle": "Usar código",
"enterCodeDescription": "Introduce el código proporcionado por tu amigo para reclamar almacenamiento gratuito para ambos",
@@ -857,7 +857,7 @@
"deviceFilesAutoUploading": "Los archivos añadidos a este álbum de dispositivo se subirán automáticamente a Ente.",
"turnOnBackupForAutoUpload": "Activar la copia de seguridad para subir automáticamente archivos añadidos a la carpeta de este dispositivo a Ente.",
"noHiddenPhotosOrVideos": "No hay fotos ni vídeos ocultos",
"toHideAPhotoOrVideo": "Para ocultar una foto o video",
"toHideAPhotoOrVideo": "Para ocultar una foto o vídeo",
"openTheItem": "• Abrir el elemento",
"clickOnTheOverflowMenu": "• Haga clic en el menú desbordante",
"click": "• Clic",
@@ -866,7 +866,7 @@
"archiveAlbum": "Archivar álbum",
"calculating": "Calculando...",
"pleaseWaitDeletingAlbum": "Por favor espera. Borrando el álbum",
"searchByExamples": "• Nombres de álbumes (por ejemplo, \"Cámara\")\n• Tipos de archivos (por ejemplo, \"Videos\", \".gif\")\n• Años y meses (por ejemplo, \"2022\", \"Enero\")\n• Vacaciones (por ejemplo, \"Navidad\")\n• Descripciones fotográficas (por ejemplo, \"#diversión\")",
"searchByExamples": "• Nombres de álbumes (por ejemplo, \"Cámara\")\n• Tipos de archivos (por ejemplo, \"Vídeos\", \".gif\")\n• Años y meses (por ejemplo, \"2022\", \"Enero\")\n• Vacaciones (por ejemplo, \"Navidad\")\n• Descripciones fotográficas (por ejemplo, \"#diversión\")",
"youCanTrySearchingForADifferentQuery": "Puedes intentar buscar una consulta diferente.",
"noResultsFound": "No se han encontrado resultados",
"addedBy": "Añadido por {emailOrName}",
@@ -884,8 +884,8 @@
"filesSavedToGallery": "Archivo guardado en la galería",
"fileFailedToSaveToGallery": "No se pudo guardar el archivo en la galería",
"download": "Descargar",
"pressAndHoldToPlayVideo": "Presiona y mantén presionado para reproducir el video",
"pressAndHoldToPlayVideoDetailed": "Mantén pulsada la imagen para reproducir el video",
"pressAndHoldToPlayVideo": "Presiona y mantén presionado para reproducir el vídeo",
"pressAndHoldToPlayVideoDetailed": "Mantén pulsada la imagen para reproducir el vídeo",
"downloadFailed": "Descarga fallida",
"deduplicateFiles": "Deduplicar archivos",
"deselectAll": "Deseleccionar todo",
@@ -895,7 +895,7 @@
"count": "Cuenta",
"totalSize": "Tamaño total",
"longpressOnAnItemToViewInFullscreen": "Manten presionado un elemento para ver en pantalla completa",
"decryptingVideo": "Descifrando video...",
"decryptingVideo": "Descifrando vídeo...",
"authToViewYourMemories": "Por favor, autentícate para ver tus recuerdos",
"unlock": "Desbloquear",
"freeUpSpace": "Liberar espacio",
@@ -1014,7 +1014,7 @@
"cachedData": "Datos almacenados en caché",
"clearCaches": "Limpiar cachés",
"remoteImages": "Imágenes remotas",
"remoteVideos": "Videos remotos",
"remoteVideos": "Vídeos remotos",
"remoteThumbnails": "Miniaturas remotas",
"pendingSync": "Sincronización pendiente",
"localGallery": "Galería local",
@@ -1169,7 +1169,7 @@
"description": "Label for the map view"
},
"maps": "Mapas",
"enableMaps": "Activar Mapas",
"enableMaps": "Habilitar mapas",
"enableMapsDesc": "Esto mostrará tus fotos en el mapa mundial.\n\nEste mapa está gestionado por Open Street Map, y la ubicación exacta de tus fotos nunca se comparte.\n\nPuedes deshabilitar esta función en cualquier momento en Ajustes.",
"quickLinks": "Acceso rápido",
"selectItemsToAdd": "Selecciona elementos para agregar",
@@ -1346,7 +1346,7 @@
"noSystemLockFound": "Bloqueo de sistema no encontrado",
"tapToUnlock": "Toca para desbloquear",
"tooManyIncorrectAttempts": "Demasiados intentos incorrectos",
"videoInfo": "Información de video",
"videoInfo": "Información de vídeo",
"autoLock": "Bloqueo automático",
"immediately": "Inmediatamente",
"autoLockFeatureDescription": "Tiempo después de que la aplicación esté en segundo plano",
@@ -1433,7 +1433,7 @@
"description": "In session page, warn user (in toast) that active sessions could not be fetched."
},
"failedToRefreshStripeSubscription": "Error al actualizar la suscripción",
"failedToPlayVideo": "Error al reproducir el video",
"failedToPlayVideo": "Error al reproducir el vídeo",
"uploadIsIgnoredDueToIgnorereason": "La subida se ignoró debido a {ignoreReason}",
"@uploadIsIgnoredDueToIgnorereason": {
"placeholders": {
@@ -1588,7 +1588,7 @@
},
"legacyInvite": "{email} te ha invitado a ser un contacto de confianza",
"authToManageLegacy": "Por favor, autentícate para administrar tus contactos de confianza",
"useDifferentPlayerInfo": "¿Tienes problemas para reproducir este video? Mantén pulsado aquí para probar un reproductor diferente.",
"useDifferentPlayerInfo": "¿Tienes problemas para reproducir este vídeo? Mantén pulsado aquí para probar un reproductor diferente.",
"hideSharedItemsFromHomeGallery": "Ocultar elementos compartidos de la galería de inicio",
"gallery": "Galería",
"joinAlbum": "Unir álbum",
@@ -1662,7 +1662,7 @@
"@linkPersonCaption": {
"description": "Caption for the 'Link person' title. It should be a continuation of the 'Link person' title. Just like how 'Link person' + 'for better sharing experience' forms a proper sentence in English, the combination of these two strings should also be a proper sentence in other languages."
},
"videoStreaming": "Vídeos en streaming",
"videoStreaming": "Vídeos en transmisión",
"processingVideos": "Procesando vídeos",
"streamDetails": "Detalles de la transmisión",
"processing": "Procesando",
@@ -1776,14 +1776,6 @@
"same": "Igual",
"different": "Diferente",
"sameperson": "la misma persona?",
"cLTitle1": "Editor avanzado de imágenes",
"cLDesc1": "Estamos lanzando un nuevo y avanzado editor de imágenes que añade más marcos de recorte, preajustes de filtros para edición rápida, opciones de ajuste finas incluyendo saturación, contraste, brillo, temperatura y mucho más. El nuevo editor también incluye la capacidad de dibujar en tus fotos y añadir emojis como pegatinas.",
"cLTitle2": "Álbumes Inteligentes",
"cLDesc2": "Ahora puedes añadir automáticamente fotos de personas seleccionadas a cualquier álbum. Solo tienes que ir al álbum, y seleccionar \"Agregar personas automáticamente\" del menú desbordante. Si se utiliza junto con el álbum compartido, puedes compartir fotos con cero clics.",
"cLTitle3": "Galería mejorada",
"cLDesc3": "Hemos añadido la capacidad de agrupar tu galería por semanas, meses y años. Ahora puedes personalizar tu galería exactamente como quieras con estas nuevas opciones de agrupación, junto con rejillas personalizadas",
"cLTitle4": "Desplazamiento más rápido",
"cLDesc4": "Junto con un montón de mejoras bajo el capó para mejorar la experiencia del desplazamiento de la galería también hemos rediseñado la barra de desplazamiento para mostrar los marcadores, permitiéndote saltar rápidamente a través de la línea de tiempo.",
"indexingPausedStatusDescription": "La indexación está pausada. Se reanudará automáticamente cuando el dispositivo esté listo. El dispositivo se considera listo cuando su nivel de batería, la salud de la batería y temperatura están en un rango saludable.",
"thisWeek": "Esta semana",
"lastWeek": "Semana pasada",
@@ -1827,5 +1819,117 @@
"type": "int"
}
}
}
},
"videosProcessed": "Vídeos procesados",
"totalVideos": "Vídeos totales",
"skippedVideos": "Vídeos omitidos",
"videoStreamingDescriptionLine1": "Reproduce vídeos al instante en cualquier dispositivo.",
"videoStreamingDescriptionLine2": "Habilitar para procesar transmisiones de vídeo en este dispositivo.",
"videoStreamingNote": "Solo los vídeos de los últimos 60 días y menos de 1 minuto se procesan en este dispositivo. Para vídeos más viejos/más largos, habilita la transmisión en la aplicación de escritorio.",
"createStream": "Crear transmisión",
"recreateStream": "Recrear transmisión",
"addedToStreamCreationQueue": "Añadido a la cola de creación de transmisiones",
"addedToStreamRecreationQueue": "Añadido a la cola de grabación de transmisiones",
"videoPreviewAlreadyExists": "La vista previa de vídeo ya existe",
"videoAlreadyInQueue": "El archivo de vídeo ya está en la cola",
"addedToQueue": "Añadido a la cola",
"creatingStream": "Creando transmisión",
"similarImages": "Imágenes similares",
"findSimilarImages": "Buscar imágenes similares",
"noSimilarImagesFound": "No se encontraron imágenes similares",
"yourPhotosLookUnique": "Tus fotos se ven únicas",
"similarGroupsFound": "{count, plural, one {}=1{{count} grupo encontrado} other{{count} grupos encontrados}}",
"@similarGroupsFound": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"reviewAndRemoveSimilarImages": "Revisar y eliminar imágenes similares",
"deletePhotosWithSize": "Eliminar {count} fotos ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "Opciones de selección",
"selectExactWithCount": "Exactamente similar ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "Seleccionar exactos",
"selectSimilarWithCount": "Mayormente, similar ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "Seleccionar similares",
"selectAllWithCount": "Todas las similitudes ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "Seleccionar imágenes similares",
"chooseSimilarImagesToSelect": "Seleccionar imágenes basándose en su similitud visual",
"clearSelection": "Borrar selección",
"similarImagesCount": "{count} imágenes similares",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "Eliminar ({count})",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "Eliminar archivos",
"areYouSureDeleteFiles": "¿Estás seguro que quieres eliminar estos archivos?",
"greatJob": "¡Bien hecho!",
"cleanedUpSimilarImages": "Has liberado {size} de espacio",
"@cleanedUpSimilarImages": {
"placeholders": {
"size": {
"type": "String"
}
}
},
"size": "Tamaño",
"similarity": "Similitud",
"processingLocally": "Procesando localmente",
"useMLToFindSimilarImages": "Revisar y eliminar imágenes que se parecen entre sí.",
"all": "Todas",
"similar": "Similares",
"identical": "Idénticas",
"nothingHereTryAnotherFilter": "Nada aquí, ¡prueba con otro filtro! 👀",
"related": "Relacionado",
"hoorayyyy": "¡Hurraaaa!",
"nothingToTidyUpHere": "Nada que limpiar aquí",
"cLTitle1": "Imágenes similares",
"cLDesc1": "Estamos introduciendo un nuevo sistema basado en ML para detectar imágenes similares, con el cual puedes limpiar tu biblioteca. Disponible en Configuración -> Copia de seguridad -> Liberar espacio",
"cLTitle2": "Mejoras de transmisión de video",
"cLDesc2": "Ahora puedes activar manualmente la generación de transmisión para videos directamente desde la aplicación. También hemos agregado una nueva pantalla de configuración de transmisión de video que te mostrará qué porcentaje de tus videos han sido procesados para transmisión",
"cLTitle3": "Mejoras de rendimiento",
"cLDesc3": "Múltiples mejoras internas, incluyendo mejor uso de caché y una experiencia de desplazamiento más fluida"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Identique",
"different": "Différent(e)",
"sameperson": "Même personne ?",
"cLTitle1": "Éditeur d'image avancé",
"cLDesc1": "Nous déployons un nouvel éditeur d'image avancé qui ajoute plus d'options de rognage, des filtres, des préréglages pour des modifications rapides ainsi que des options de réglage fin (la saturation, le contraste, la luminosité, la température et beaucoup plus). Le nouvel éditeur inclut également la possibilité de dessiner sur vos photos et d'ajouter des emojis en tant qu'autocollants.",
"cLTitle2": "Albums Intelligents",
"cLDesc2": "Vous pouvez maintenant ajouter automatiquement des photos de personnes sélectionnées à n'importe quel album. Allez simplement à l'album et sélectionnez \"Ajouter automatiquement des personnes\" dans le menu déroulant. Couplé avec un album partagé, vous pouvez partager des photos en zéro clic.",
"cLTitle3": "Galerie améliorée",
"cLDesc3": "Nous avons ajouté la possibilité de regrouper votre galerie par semaines, mois et années. Vous pouvez maintenant personnaliser votre galerie pour qu'elle soit exactement comme vous le souhaitez avec ces nouvelles options de regroupement, ainsi que des grilles personnalisées",
"cLTitle4": "Défilement plus rapide",
"cLDesc4": "En plus des quelques améliorations pour améliorer l'expérience de défilement de la galerie, nous avons également redessiné la barre de défilement pour afficher des marqueurs, ce qui vous permet de sauter rapidement dans la chronologie.",
"indexingPausedStatusDescription": "L'indexation est en pause. Elle reprendra automatiquement lorsque l'appareil sera prêt. Celui-ci est considéré comme prêt lorsque le niveau de batterie, sa santé et son état thermique sont dans une plage saine.",
"thisWeek": "Cette semaine",
"lastWeek": "La semaine dernière",
@@ -1827,5 +1819,109 @@
"type": "int"
}
}
}
},
"videosProcessed": "Vidéos traitées",
"totalVideos": "Total de vidéos",
"skippedVideos": "Vidéos ignorées",
"videoStreamingDescriptionLine1": "Lire instantanément des vidéos sur n'importe quel appareil.",
"videoStreamingDescriptionLine2": "Activer pour traiter les flux vidéo sur cet appareil.",
"videoStreamingNote": "Seules les vidéos des 60 derniers jours et de moins d'une minute sont traitées sur cet appareil. Pour les vidéos plus anciennes/plus longues, activez le streaming dans l'application pour ordinateur.",
"createStream": "Créer le flux",
"recreateStream": "Recréer le flux",
"addedToStreamCreationQueue": "Ajouté à la file d'attente de création de flux",
"addedToStreamRecreationQueue": "Ajouté à la file d'attente de re-création de flux",
"videoPreviewAlreadyExists": "L'aperçu vidéo existe déjà",
"videoAlreadyInQueue": "Fichier vidéo déjà présent dans la file d'attente",
"addedToQueue": "Ajouté à la file d'attente",
"creatingStream": "Création du flux",
"similarImages": "Images similaires",
"findSimilarImages": "Rechercher des images similaires",
"noSimilarImagesFound": "Aucune image similaire trouvée",
"yourPhotosLookUnique": "Vos photos semblent uniques",
"reviewAndRemoveSimilarImages": "Examiner et supprimer les images similaires",
"deletePhotosWithSize": "Supprimer {count} photos ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "Options de sélection",
"selectExactWithCount": "Exactement similaire ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "Sélectionner exactement",
"selectSimilarWithCount": "Plutôt similaire ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "Sélectionner à l'identique",
"selectAllWithCount": "Toutes les similarités ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "Sélectionner les images similaires",
"chooseSimilarImagesToSelect": "Sélectionnez des images en fonction de leur similitude visuelle",
"clearSelection": "Effacer la sélection",
"similarImagesCount": "{count} images similaires",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "Supprimer ({count})",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "Supprimer les fichiers",
"areYouSureDeleteFiles": "Êtes-vous sûr de vouloir supprimer ces fichiers ?",
"greatJob": "Excellent !",
"cleanedUpSimilarImages": "Vous avez libéré {size} d'espace",
"@cleanedUpSimilarImages": {
"placeholders": {
"size": {
"type": "String"
}
}
},
"size": "Taille",
"similarity": "Similitude",
"processingLocally": "Traitement local",
"useMLToFindSimilarImages": "Examinez et supprimez les images qui se ressemblent entre elles.",
"all": "Toutes",
"similar": "Similaires",
"identical": "Identiques",
"nothingHereTryAnotherFilter": "Rien ici, essayez un autre filtre ! 👀",
"related": "Liés",
"hoorayyyy": "Houraaa !",
"nothingToTidyUpHere": "Rien à nettoyer ici",
"cLTitle1": "Images similaires",
"cLDesc1": "Nous introduisons un nouveau système basé sur l'IA pour détecter les images similaires, avec lequel vous pouvez nettoyer votre bibliothèque. Disponible dans Paramètres -> Sauvegarde -> Libérer de l'espace",
"cLTitle2": "Améliorations de la diffusion vidéo",
"cLDesc2": "Vous pouvez maintenant déclencher manuellement la génération de flux pour les vidéos directement depuis l'application. Nous avons également ajouté un nouvel écran de paramètres de diffusion vidéo qui vous montrera quel pourcentage de vos vidéos ont été traitées pour la diffusion",
"cLTitle3": "Améliorations des performances",
"cLDesc3": "Plusieurs améliorations internes, incluant une meilleure utilisation du cache et une expérience de défilement plus fluide"
}

View File

@@ -189,6 +189,7 @@
"allowAddPhotosDescription": "Izinkan orang yang memiliki link untuk menambahkan foto ke album berbagi ini.",
"passwordLock": "Kunci dengan sandi",
"canNotOpenTitle": "Tidak dapat membuka album ini",
"canNotOpenBody": "Maaf, album ini tidak dapat dibuka di aplikasi.",
"disableDownloadWarningTitle": "Perlu diketahui",
"disableDownloadWarningBody": "Orang yang melihat masih bisa mengambil tangkapan layar atau menyalin foto kamu menggunakan alat eksternal",
"allowDownloads": "Izinkan pengunduhan",
@@ -355,6 +356,7 @@
"failedToLoadAlbums": "Gagal memuat album",
"hidden": "Tersembunyi",
"authToViewYourHiddenFiles": "Harap autentikasi untuk melihat file tersembunyi kamu",
"authToViewTrashedFiles": "Silakan autentikasi untuk melihat file anda yang ada di tong sampah",
"trash": "Sampah",
"uncategorized": "Tak Berkategori",
"videoSmallCase": "video",
@@ -370,6 +372,21 @@
"deleteFromBoth": "Hapus dari keduanya",
"newAlbum": "Album baru",
"albums": "Album",
"memoryCount": "{count, plural, =0{tidak ada memori} one{{formattedCount} memori} other{{formattedCount} memori}}",
"@memoryCount": {
"description": "The text to display the number of memories",
"type": "text",
"placeholders": {
"count": {
"example": "1",
"type": "int"
},
"formattedCount": {
"type": "String",
"example": "11.513, 11,511"
}
}
},
"selectedPhotos": "{count} terpilih",
"@selectedPhotos": {
"description": "Display the number of selected photos",
@@ -419,6 +436,7 @@
"discover_receipts": "Tanda Terima",
"discover_notes": "Catatan",
"discover_memes": "Meme",
"discover_visiting_cards": "Kartu Nama",
"discover_babies": "Bayi",
"discover_pets": "Hewan",
"discover_selfies": "Swafoto",
@@ -427,6 +445,7 @@
"discover_celebrations": "Perayaan",
"discover_sunset": "Senja",
"discover_hills": "Bukit",
"discover_greenery": "Hijau-hijauan",
"mlIndexingDescription": "Perlu diperhatikan bahwa pemelajaran mesin dapat meningkatkan penggunaan data dan baterai perangkat hingga seluruh item selesai terindeks. Gunakan aplikasi desktop untuk pengindeksan lebih cepat, seluruh hasil akan tersinkronkan secara otomatis.",
"loadingModel": "Mengunduh model...",
"waitingForWifi": "Menunggu WiFi...",
@@ -442,6 +461,21 @@
"updatingFolderSelection": "Memperbaharui pilihan folder...",
"itemCount": "{count, plural, other{{count} item}}",
"deleteItemCount": "{count, plural, =1 {Hapus {count} item} other {Hapus {count} item}}",
"duplicateItemsGroup": "{count} berkas, masing-masing {formattedSize}",
"@duplicateItemsGroup": {
"description": "Display the number of duplicate files and their size",
"type": "text",
"placeholders": {
"count": {
"example": "12",
"type": "int"
},
"formattedSize": {
"example": "2.3 MB",
"type": "String"
}
}
},
"showMemories": "Lihat kenangan",
"yearsAgo": "{count, plural, other{{count} tahun lalu}}",
"backupSettings": "Pengaturan pencadangan",
@@ -492,6 +526,7 @@
"viewLargeFiles": "File berukuran besar",
"viewLargeFilesDesc": "Tampilkan file yang paling besar mengonsumsi ruang penyimpanan.",
"noDuplicates": "✨ Tak ada file duplikat",
"youveNoDuplicateFilesThatCanBeCleared": "Anda tidak memiliki file duplikat yang dapat dihapus",
"success": "Berhasil",
"rateUs": "Beri kami nilai",
"remindToEmptyDeviceTrash": "Kosongkan juga “Baru Dihapus” dari “Pengaturan” -> “Penyimpanan” untuk memperoleh ruang yang baru saja dibersihkan",
@@ -658,6 +693,7 @@
"rateTheApp": "Nilai app ini",
"startBackup": "Mulai pencadangan",
"noPhotosAreBeingBackedUpRightNow": "Tidak ada foto yang sedang dicadangkan sekarang",
"preserveMore": "Amankan lebih banyak",
"grantFullAccessPrompt": "Harap berikan akses ke semua foto di app Pengaturan",
"allowPermTitle": "Izinkan akses ke foto",
"allowPermBody": "Ijinkan akses ke foto Anda dari Pengaturan agar Ente dapat menampilkan dan mencadangkan pustaka Anda.",
@@ -714,6 +750,7 @@
"lastUpdated": "Terakhir diperbarui",
"deleteEmptyAlbums": "Hapus album kosong",
"deleteEmptyAlbumsWithQuestionMark": "Hapus album yang kosong?",
"deleteAlbumsDialogBody": "Ini akan menghapus semua album kosong. Ini berguna ketika anda ingin mengurangi kekacauan di daftar album anda.",
"deleteProgress": "Menghapus {currentlyDeleting} / {totalCount}",
"genericProgress": "Memproses {currentlyProcessing} / {totalCount}",
"@genericProgress": {
@@ -731,6 +768,7 @@
}
},
"permanentlyDelete": "Hapus secara permanen",
"canOnlyCreateLinkForFilesOwnedByYou": "Hanya dapat membuat tautan untuk file yang dimiliki oleh anda",
"publicLinkCreated": "Link publik dibuat",
"youCanManageYourLinksInTheShareTab": "Kamu bisa atur link yang telah kamu buat di tab berbagi.",
"linkCopiedToClipboard": "Link tersalin ke papan klip",
@@ -740,15 +778,30 @@
"type": "text"
},
"moveToAlbum": "Pindahkan ke album",
"unhide": "Tampilkan",
"unarchive": "Keluarkan dari arsip",
"favorite": "Favorit",
"removeFromFavorite": "Hapus dari favorit",
"shareLink": "Bagikan link",
"createCollage": "Buat kolase",
"saveCollage": "Simpan kolase",
"collageSaved": "Kolase tersimpan ke galeri",
"collageLayout": "Tata letak",
"addToEnte": "Tambah ke Ente",
"addToAlbum": "Tambah ke album",
"delete": "Hapus",
"hide": "Sembunyikan",
"share": "Bagikan",
"unhideToAlbum": "Tampikan ke album",
"restoreToAlbum": "Pulihkan ke album",
"moveItem": "{count, plural, =1 {Pindahkan item} other {Pindahkan item}}",
"@moveItem": {
"description": "Page title while moving one or more items to an album"
},
"addItem": "{count, plural, =1 {Tambahkan item} other {Tambahkan item}}",
"@addItem": {
"description": "Page title while adding one or more items to album"
},
"createOrSelectAlbum": "Buat atau pilih album",
"selectAlbum": "Pilih album",
"searchByAlbumNameHint": "Nama album",
@@ -756,18 +809,33 @@
"enterAlbumName": "Masukkan nama album",
"restoringFiles": "Memulihkan file...",
"movingFilesToAlbum": "Memindahkan file ke album...",
"unhidingFilesToAlbum": "Tampilkan berkas ke album",
"canNotUploadToAlbumsOwnedByOthers": "Tidak dapat mengunggah album yang dimiliki oleh orang lain",
"uploadingFilesToAlbum": "Mengunggah file ke album...",
"addedSuccessfullyTo": "Berhasil ditambahkan ke {albumName}",
"movedSuccessfullyTo": "Berhasil dipindahkan ke {albumName}",
"thisAlbumAlreadyHDACollaborativeLink": "Link kolaborasi untuk album ini sudah terbuat",
"collaborativeLinkCreatedFor": "Link kolaborasi terbuat untuk {albumName}",
"askYourLovedOnesToShare": "Minta orang terkasih anda untuk berbagi",
"invite": "Undang",
"shareYourFirstAlbum": "Bagikan album pertamamu",
"sharedWith": "Dibagikan dengan {emailIDs}",
"sharedWithMe": "Dibagikan dengan saya",
"sharedByMe": "Dibagikan oleh saya",
"doubleYourStorage": "Gandakan kuota kamu",
"referFriendsAnd2xYourPlan": "Ajak teman dan gandakan paket anda",
"shareAlbumHint": "Buka album lalu ketuk tombol bagikan di sudut kanan atas untuk berbagi.",
"itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Item menampilkan jumlah hari yang tersisa sebelum dihapus permanen",
"trashDaysLeft": "{count, plural, =0 {Segera} =1 {1 hari} other {{count} hari}}",
"@trashDaysLeft": {
"description": "Text to indicate number of days remaining before permanent deletion",
"placeholders": {
"count": {
"example": "1|2|3",
"type": "int"
}
}
},
"deleteAll": "Hapus Semua",
"renameAlbum": "Ubah nama album",
"convertToAlbum": "Ubah menjadi album",
@@ -782,13 +850,16 @@
"leaveSharedAlbum": "Tinggalkan album bersama?",
"leaveAlbum": "Tinggalkan album",
"photosAddedByYouWillBeRemovedFromTheAlbum": "Foto yang telah kamu tambahkan akan dihapus dari album ini",
"youveNoFilesInThisAlbumThatCanBeDeleted": "Anda tidak memiliki file di album ini yang dapat dihapus",
"youDontHaveAnyArchivedItems": "Kamu tidak memiliki item di arsip.",
"ignoredFolderUploadReason": "Sejumlah file di album ini tidak terunggah karena telah dihapus sebelumnya dari Ente.",
"resetIgnoredFiles": "Atur ulang file yang diabaikan",
"deviceFilesAutoUploading": "File yang ditambahkan ke album perangkat ini akan diunggah ke Ente secara otomatis.",
"turnOnBackupForAutoUpload": "Aktifkan pencadangan untuk mengunggah file yang ditambahkan ke folder ini ke Ente secara otomatis.",
"noHiddenPhotosOrVideos": "Tidak ada foto atau video tersembunyi",
"toHideAPhotoOrVideo": "Untuk menyembunyikan foto atau video",
"openTheItem": "• Buka item-nya",
"clickOnTheOverflowMenu": "• Klik pada menu overflow",
"click": "• Click",
"nothingToSeeHere": "Tidak ada apa-apa di sini! 👀",
"unarchiveAlbum": "Keluarkan album dari arsip",
@@ -796,6 +867,7 @@
"calculating": "Menghitung...",
"pleaseWaitDeletingAlbum": "Harap tunggu, sedang menghapus album",
"searchByExamples": "• Nama album (cth. \"Kamera\")\n• Jenis file (cth. \"Video\", \".gif\")\n• Tahun atau bulan (cth. \"2022\", \"Januari\")\n• Musim liburan (cth. \"Natal\")\n• Keterangan foto (cth. “#seru”)",
"youCanTrySearchingForADifferentQuery": "Anda dapat mencoba mencari dengan kata-kata yang berbeda",
"noResultsFound": "Tidak ditemukan hasil",
"addedBy": "Ditambahkan oleh {emailOrName}",
"loadingExifData": "Memuat data EXIF...",
@@ -804,6 +876,7 @@
"thisImageHasNoExifData": "Gambar ini tidak memiliki data exif",
"exif": "EXIF",
"noResults": "Tidak ada hasil",
"weDontSupportEditingPhotosAndAlbumsThatYouDont": "Kami belum mendukung pengeditan foto dan album yang bukan milik anda",
"failedToFetchOriginalForEdit": "Gagal memuat file asli untuk mengedit",
"close": "Tutup",
"setAs": "Pasang sebagai",
@@ -814,12 +887,19 @@
"pressAndHoldToPlayVideo": "Tekan dan tahan untuk memutar video",
"pressAndHoldToPlayVideoDetailed": "Tekan dan tahan gambar untuk memutar video",
"downloadFailed": "Gagal mengunduh",
"deduplicateFiles": "Hilangkan Duplikasi File",
"deselectAll": "Batalkan semua pilihan",
"reviewDeduplicateItems": "Silakan lihat dan hapus item yang merupakan duplikat.",
"clubByCaptureTime": "Kelompokkan berdasarkan waktu pengambilan",
"clubByFileName": "Kelompokkan berdasarkan nama berkas",
"count": "Jumlah",
"totalSize": "Ukuran total",
"longpressOnAnItemToViewInFullscreen": "Tekan lama pada item untuk melihat dalam layar penuh",
"decryptingVideo": "Mendekripsi video...",
"authToViewYourMemories": "Harap autentikasi untuk melihat kenanganmu",
"unlock": "Buka",
"freeUpSpace": "Bersihkan ruang",
"freeUpSpaceSaving": "{count, plural, =1 {Itu dapat dihapus dari perangkat untuk mengosongkan {formattedSize}} other {Itu dapat dihapus dari perangkat untuk mengosongkan {formattedSize}}}",
"filesBackedUpInAlbum": "{count, plural, other {{formattedNumber} file}} dalam album ini telah berhasil dicadangkan",
"@filesBackedUpInAlbum": {
"description": "Text to tell user how many files have been backed up in the album",
@@ -850,11 +930,24 @@
}
}
},
"@freeUpSpaceSaving": {
"description": "Text to tell user how much space they can free up by deleting items from the device"
},
"freeUpAccessPostDelete": "anda masih dapat mengakses {count, plural, =1 {itu} other {mereka}} di Ente selama anda memiliki langganan aktif",
"@freeUpAccessPostDelete": {
"placeholders": {
"count": {
"example": "1",
"type": "int"
}
}
},
"freeUpAmount": "Bersihkan {sizeInMBorGB}",
"thisEmailIsAlreadyInUse": "Email ini telah digunakan",
"incorrectCode": "Kode salah",
"authenticationFailedPleaseTryAgain": "Autentikasi gagal, silakan coba lagi",
"verificationFailedPleaseTryAgain": "Verifikasi gagal, silakan coba lagi",
"authenticating": "Autentikasi...",
"authenticationSuccessful": "Autentikasi berhasil!",
"incorrectRecoveryKey": "Kunci pemulihan salah",
"theRecoveryKeyYouEnteredIsIncorrect": "Kunci pemulihan yang kamu masukkan salah",
@@ -865,12 +958,35 @@
"sorryTheCodeYouveEnteredIsIncorrect": "Maaf, kode yang kamu masukkan salah",
"yourVerificationCodeHasExpired": "Kode verifikasi kamu telah kedaluwarsa",
"emailChangedTo": "Email diubah menjadi {newEmail}",
"verifying": "Memverifikasi...",
"disablingTwofactorAuthentication": "Menonaktifkan autentikasi dua langkah...",
"allMemoriesPreserved": "Semua kenangan terpelihara",
"loadingGallery": "Memuat galeri...",
"syncing": "Menyinkronkan...",
"encryptingBackup": "Mengenkripsi cadangan...",
"syncStopped": "Sinkronisasi terhenti",
"syncProgress": "{completed}/{total} memori tersimpan",
"uploadingMultipleMemories": "Menyimpan {count} memori...",
"@uploadingMultipleMemories": {
"description": "Text to tell user how many memories are being preserved",
"placeholders": {
"count": {
"type": "String"
}
}
},
"uploadingSingleMemory": "Menyimpan 1 memori...",
"@syncProgress": {
"description": "Text to tell user how many memories have been preserved",
"placeholders": {
"completed": {
"type": "String"
},
"total": {
"type": "String"
}
}
},
"archiving": "Mengarsipkan...",
"unarchiving": "Mengeluarkan dari arsip...",
"successfullyArchived": "Berhasil diarsipkan",
@@ -885,6 +1001,8 @@
"empty": "Kosongkan",
"couldNotFreeUpSpace": "Tidak dapat membersihkan ruang",
"permanentlyDeleteFromDevice": "Hapus dari perangkat secara permanen?",
"someOfTheFilesYouAreTryingToDeleteAre": "Beberapa file yang anda coba hapus hanya tersedia di perangkat anda dan tidak dapat dipulihkan jika dihapus",
"theyWillBeDeletedFromAllAlbums": "Mereka akan dihapus dari semua album.",
"someItemsAreInBothEnteAndYourDevice": "Sejumlah item tersimpan di Ente serta di perangkat ini.",
"selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Item terpilih akan dihapus dari semua album dan dipindahkan ke sampah.",
"theseItemsWillBeDeletedFromYourDevice": "Item ini akan dihapus dari perangkat ini.",
@@ -894,12 +1012,17 @@
"networkHostLookUpErr": "Tidak dapat terhubung dengan Ente, harap periksa pengaturan jaringan kamu dan hubungi dukungan jika masalah berlanjut.",
"networkConnectionRefusedErr": "Tidak dapat terhubung dengan Ente, silakan coba lagi setelah beberapa saat. Jika masalah berlanjut, harap hubungi dukungan.",
"cachedData": "Data cache",
"clearCaches": "Bersihkan cache",
"remoteImages": "Gambar jarak jauh",
"remoteVideos": "Video jarak jauh",
"remoteThumbnails": "Thumbnail jarak jauh",
"pendingSync": "Sinkronisasi tertunda",
"localGallery": "Galeri lokal",
"todaysLogs": "Log hari ini",
"viewLogs": "Lihat log",
"logsDialogBody": "Ini akan mengirimkan log untuk membantu kami memperbaiki masalah anda. Harap diperhatikan bahwa nama file akan disertakan untuk membantu melacak masalah pada file tertentu.",
"preparingLogs": "Menyiapkan log...",
"emailYourLogs": "Kirim log anda melalui email",
"pleaseSendTheLogsTo": "Silakan kirim log-nya ke \n{toEmail}",
"copyEmailAddress": "Salin alamat email",
"exportLogs": "Ekspor log",

View File

@@ -1745,5 +1745,11 @@
"birthdayNotifications": "Notifiche dei compleanni",
"receiveRemindersOnBirthdays": "Ricevi promemoria quando è il compleanno di qualcuno. Toccare la notifica ti porterà alle foto della persona che compie gli anni.",
"happyBirthday": "Buon compleanno! 🥳",
"birthdays": "Compleanni"
"birthdays": "Compleanni",
"cLTitle1": "Immagini simili",
"cLDesc1": "Stiamo introducendo un nuovo sistema basato su ML per rilevare immagini simili, con il quale puoi pulire la tua libreria. Disponibile in Impostazioni -> Backup -> Libera spazio",
"cLTitle2": "Miglioramenti streaming video",
"cLDesc2": "Ora puoi attivare manualmente la generazione di stream per i video direttamente dall'app. Abbiamo anche aggiunto una nuova schermata delle impostazioni di streaming video che ti mostrerà quale percentuale dei tuoi video è stata elaborata per lo streaming",
"cLTitle3": "Miglioramenti delle prestazioni",
"cLDesc3": "Multipli miglioramenti interni, incluso un miglior utilizzo della cache e un'esperienza di scorrimento più fluida"
}

View File

@@ -1665,5 +1665,11 @@
"moon": "月明かりの中",
"onTheRoad": "再び道で",
"food": "料理を楽しむ",
"pets": "毛むくじゃらな仲間たち"
"pets": "毛むくじゃらな仲間たち",
"cLTitle1": "類似画像",
"cLDesc1": "類似画像を検出する新しいML基盤システムを導入し、ライブラリをクリーンアップできます。設定 -> バックアップ -> 容量を空ける で利用可能",
"cLTitle2": "動画ストリーミングの強化",
"cLDesc2": "アプリから直接、動画のストリーム生成を手動でトリガーできるようになりました。また、動画のうち何パーセントがストリーミング用に処理されたかを表示する新しい動画ストリーミング設定画面も追加しました",
"cLTitle3": "パフォーマンスの改善",
"cLDesc3": "より良いキャッシュ使用とよりスムーズなスクロール体験を含む、複数の内部改善"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Tas pats",
"different": "Skirtingas",
"sameperson": "Tas pats asmuo?",
"cLTitle1": "Pažangi vaizdų rengyklė",
"cLDesc1": "Mes išleidžiame naują ir pažangią vaizdų rengyklę, kurioje yra daugiau apkirpimo rėmelių, filtro nustatymų sparčiams redagavimams, tikslaus sureguliavimo parinkčių, įskaitant sodrumą, kontrastą, skaistį, temperatūrą ir daug daugiau. Naujoji rengyklė taip pat suteikia galimybę piešti ant nuotraukų ir pridėti jaustukus kaip lipdukus.",
"cLTitle2": "Išmanieji albumai",
"cLDesc2": "Dabar galite automatiškai įtraukti pasirinktų asmenų nuotraukas į bet kurį albumą. Tiesiog eikite į albumą ir iš išskleidžiamojo meniu pasirinkite „Automatiškai įtraukti asmenis“. Jei naudojama kartu su bendrinimu albumu, nuotraukas galite bendrinti be jokių paspaudimų.",
"cLTitle3": "Patobulinta galerija",
"cLDesc3": "Pridėjome galimybę sugrupuoti galeriją pagal savaites, mėnesius ir metus. Dabar galite pritaikyti galeriją taip, kad ji atrodytų būtent taip, kaip norite su šiomis naujomis grupavimo parinktimis ir pasirinktiniais tinkleliais.",
"cLTitle4": "Spartesnis slinkimas",
"cLDesc4": "Kartu su daugybe vidinių patobulinimų pagerinti galerijos slinkimo patirtį, mes taip pat pertvarkėme slinkties juostą, kad joje būtų rodomi žymekliai, leidžiantys sparčiai pereiti per laiko juostą.",
"indexingPausedStatusDescription": "Indeksavimas pristabdytas. Jis bus automatiškai tęsiamas, kai įrenginys bus parengtas. Įrenginys laikomas parengtu, kai jo akumuliatoriaus įkrovos lygis, akumuliatoriaus būklė ir terminė būklė yra normos ribose.",
"thisWeek": "Šią savaitę",
"lastWeek": "Praėjusią savaitę",
@@ -1818,5 +1810,16 @@
"brushColor": "Teptuko spalva",
"font": "Šriftas",
"background": "Fonas",
"align": "Lygiuoti"
}
"align": "Lygiuoti",
"similarImages": "Panašūs vaizdai",
"findSimilarImages": "Rasti panašų vaizdų",
"noSimilarImagesFound": "Panašių vaizdų nerasta",
"yourPhotosLookUnique": "Jūsų nuotraukos atrodo ypatingos",
"selectionOptions": "Pasirinkimo parinktys",
"deleteFiles": "Ištrinti failus",
"areYouSureDeleteFiles": "Ar tikrai norite ištrinti šiuos failus?",
"greatJob": "Puiku!",
"size": "Dydis",
"similarity": "Panašumas",
"processingLocally": "Apdorojama vietoje"
}

View File

@@ -1772,5 +1772,11 @@
"thePersonWillNotBeDisplayed": "De persoon wordt niet meer getoond in de personen sectie. Foto's blijven ongemoeid.",
"areYouSureYouWantToMergeThem": "Weet je zeker dat je ze wilt samenvoegen?",
"allUnnamedGroupsWillBeMergedIntoTheSelectedPerson": "Alle naamloze groepen worden samengevoegd met de geselecteerde persoon. Dit kan nog steeds ongedaan worden gemaakt vanuit het geschiedenisoverzicht van de persoon.",
"yesIgnore": "Ja, negeer"
"yesIgnore": "Ja, negeer",
"cLTitle1": "Vergelijkbare afbeeldingen",
"cLDesc1": "We introduceren een nieuw ML-gebaseerd systeem om vergelijkbare afbeeldingen te detecteren, waarmee je je bibliotheek kunt opschonen. Beschikbaar in Instellingen -> Backup -> Ruimte vrijmaken",
"cLTitle2": "Video streaming verbeteringen",
"cLDesc2": "Je kunt nu handmatig stream generatie voor video's activeren direct vanuit de app. We hebben ook een nieuw video streaming instellingenscherm toegevoegd dat toont welk percentage van je video's is verwerkt voor streaming",
"cLTitle3": "Prestatieverbeteringen",
"cLDesc3": "Meerdere verbeteringen onder de motorkap, inclusief beter cache gebruik en een vloeiendere scroll ervaring"
}

View File

@@ -1736,5 +1736,11 @@
"albumsWidgetDesc": "Velg albumene du ønsker å se på din hjemskjerm.",
"memoriesWidgetDesc": "Velg typen minner du ønsker å se på din hjemskjerm.",
"smartMemories": "Smarte minner",
"pastYearsMemories": "Tidligere års minner"
"pastYearsMemories": "Tidligere års minner",
"cLTitle1": "Lignende bilder",
"cLDesc1": "Vi introduserer et nytt ML-basert system for å oppdage lignende bilder, som du kan bruke til å rydde opp i biblioteket ditt. Tilgjengelig i Innstillinger -> Sikkerhetskopi -> Frigjør plass",
"cLTitle2": "Video streaming forbedringer",
"cLDesc2": "Du kan nå manuelt utløse stream generering for videoer direkte fra appen. Vi har også lagt til en ny video streaming innstillinger skjerm som viser deg hvor mange prosent av videoene dine som er behandlet for streaming",
"cLTitle3": "Ytelsesforbedringer",
"cLDesc3": "Flere forbedringer under panseret, inkludert bedre cache bruk og en jevnere rullingsopplevelse"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Identyczne",
"different": "Inne",
"sameperson": "Ta sama osoba?",
"cLTitle1": "Zaawansowany Edytor Obrazów",
"cLDesc1": "Wydajemy nowy i zaawansowany edytor obrazów, który dodaje więcej klatek przycinania, filtry dla szybkich edycji, precyzyjne opcje dostrajania, w tym nasycenie, kontrast, jasność, temperatura i wiele więcej. Nowy edytor zawiera również możliwość rysowania zdjęć i dodawania emotikonów jako naklejki.",
"cLTitle2": "Inteligentne Albumy",
"cLDesc2": "Teraz możesz automatycznie dodawać zdjęcia wybranych osób do dowolnego albumu. Po prostu przejdź do albumu i wybierz \"automatycznie dodaj osoby\" z menu przepełnienia. Jeśli używane razem z udostępnionym albumem, możesz udostępniać zdjęcia bez żadnych kliknięć.",
"cLTitle3": "Ulepszona Galeria",
"cLDesc3": "Dodaliśmy możliwość grupowania Twojej galerii po tygodniach, miesiącach i latach. Możesz teraz spersonalizować swoją galerię, aby dokładnie wyglądać w ten sposób z nowymi opcjami grupowania, wraz z niestandardowymi siatkami",
"cLTitle4": "Szybsze Przewijanie",
"cLDesc4": "Wraz z kilkoma ulepszeniami w celu poprawy doświadczenia galerii, przeprojektowaliśmy również pasek przewijania, aby pokazywać znaczniki, umożliwiając szybki skok po osi czasu.",
"indexingPausedStatusDescription": "Indeksowanie zostało wstrzymane. Zostanie automatycznie wznowione, gdy urządzenie będzie gotowe. Urządzenie uznaje się za gotowe, gdy poziom baterii, stan jej zdrowia oraz status termiczny znajdują się w bezpiecznym zakresie.",
"thisWeek": "Ten tydzień",
"lastWeek": "Zeszły tydzień",
@@ -1827,5 +1819,11 @@
"type": "int"
}
}
}
},
"cLTitle1": "Podobne obrazy",
"cLDesc1": "Wprowadzamy nowy system oparty na ML do wykrywania podobnych obrazów, za pomocą którego możesz posprzątać swoją bibliotekę. Dostępne w Ustawienia->Kopia zapasowa->Zwolnij miejsce",
"cLTitle2": "Ulepszenia streamingu wideo",
"cLDesc2": "Możesz teraz ręcznie wyzwolić generowanie strumienia dla filmów bezpośrednio z aplikacji. Dodaliśmy również nowy ekran ustawień streamingu wideo, który pokaże ci, jaki procent twoich filmów zostało przetworzonych do streamingu",
"cLTitle3": "Ulepszenia wydajności",
"cLDesc3": "Liczne ulepszenia pod maską, w tym lepsze wykorzystanie pamięci podręcznej i płynniejsze przewijanie"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Igual",
"different": "Diferente",
"sameperson": "Mesma pessoa?",
"cLTitle1": "Editor de Imagens Avançado",
"cLDesc1": "Estamos lançando um novo editor de fotos avançado que adiciona mais quadros de recorte, predefinições de filtro para edições rápidas, ajustes para afinação incluindo saturação, contraste, brilho, temperatura e mais. O novo editor também incluí a habilidade de desenhar em suas fotos e adicionar emojis como figurinhas.",
"cLTitle2": "Álbuns Inteligentes",
"cLDesc2": "Você agora pode adicionar automaticamente fotos de pessoas selecionadas para qualquer álbum. É só ir ao álbum, selecionar \"adicionar pessoa auto.\" no menu avançado. Se usado junto ao álbum compartilhado, você pode compartilhar fotos sem maior esforço.",
"cLTitle3": "Galeria Aprimorada",
"cLDesc3": "Adicionamos a habilidade de agrupar sua galeria por semanas, meses, e anos. Você pode personalizar sua galeria para parecer exatamente a maneira que desejar usando as novas opções de agrupamento, junto às grades personalizadas",
"cLTitle4": "Arrastar Rápido",
"cLDesc4": "Junto ao tanto de melhorias salva-vidas para melhorar a experiência de arraste na galeria, também redesenhamos a barra de deslize para exibir marcadores, permitindo você pular a timeline rapidamente.",
"indexingPausedStatusDescription": "A indexação foi pausada. Ela retomará automaticamente quando o dispositivo estiver pronto. O dispositivo é considerado pronto quando o nível de bateria, saúde da bateria, e estado térmico estejam num alcance saudável.",
"thisWeek": "Esta semana",
"lastWeek": "Semana passada",
@@ -1827,5 +1819,11 @@
"type": "int"
}
}
}
}
},
"cLTitle1": "Imagens similares",
"cLDesc1": "Estamos introduzindo um novo sistema baseado em ML para detectar imagens similares, com o qual você pode limpar sua biblioteca. Disponível em Configurações -> Backup -> Liberar espaço",
"cLTitle2": "Melhorias do streaming de vídeo",
"cLDesc2": "Agora você pode acionar manualmente a geração de stream para vídeos diretamente do aplicativo. Também adicionamos uma nova tela de configurações de streaming de vídeo que mostrará qual porcentagem dos seus vídeos foram processados para streaming",
"cLTitle3": "Melhorias de desempenho",
"cLDesc3": "Múltiplas melhorias internas, incluindo melhor uso de cache e uma experiência de rolagem mais suave"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Igual",
"different": "Diferente",
"sameperson": "A mesma pessoa?",
"cLTitle1": "Editor de Imagens Avançado",
"cLDesc1": "Estamos a lançar um novo editor avançado que adiciona mais ecrãs de recorte, predefinições de filtro para edições ágeis, ajustes de afinação incluindo saturação, contraste, brilho, temperatura e mais além. O novo editor também será possível desenhar nas suas fotos e adicionar emojis como autocolantes.",
"cLTitle2": "Álbuns Inteligentes",
"cLDesc2": "Agora pode automaticamente adicionar fotos de pessoas selecionadas para qualquer álbum. É só ir até o álbum, e clicar \"auto adicionar pessoa\" no menu expandido. Se usado com o álbum, pode partilhar fotos sem esforço.",
"cLTitle3": "Fototeca Improvisada",
"cLDesc3": "Adicionamos o agrupamento à sua fototeca, com filtro de semanas, meses, e anos. Pode personalizar a sua fototeca para parecer como desejar ao usar as novas definições de agrupamento, junto às grades personalizadas",
"cLTitle4": "Arraste Ágil",
"cLDesc4": "Junto às improvisações salva-vidas para melhorar a experiência de arraste na fototeca, também redesenhamos o slider para mostrar marcadores, permitindo você pular a linha do tempo mais fácil.",
"indexingPausedStatusDescription": "A indexação foi interrompida. Ele será retomado se o dispositivo estiver pronto. O dispositivo é considerado pronto se o nível de bateria, saúde da bateria, e estado térmico esteja num estado saudável.",
"thisWeek": "Esta semana",
"lastWeek": "Semana passada",
@@ -1827,5 +1819,11 @@
"type": "int"
}
}
}
}
},
"cLTitle1": "Imagens similares",
"cLDesc1": "Estamos a introduzir um novo sistema baseado em ML para detectar imagens similares, com o qual pode limpar a sua biblioteca. Disponível em Definições -> Cópia de segurança -> Libertar espaço",
"cLTitle2": "Melhorias do streaming de vídeo",
"cLDesc2": "Agora pode accionar manualmente a geração de stream para vídeos directamente da aplicação. Também adicionámos um novo ecrã de definições de streaming de vídeo que mostrará que percentagem dos seus vídeos foram processados para streaming",
"cLTitle3": "Melhorias de desempenho",
"cLDesc3": "Múltiplas melhorias internas, incluindo melhor uso de cache e uma experiência de deslocação mais suave"
}

View File

@@ -1521,5 +1521,11 @@
"joinAlbum": "Alăturați-vă albumului",
"joinAlbumSubtext": "pentru a vedea și a adăuga fotografii",
"joinAlbumSubtextViewer": "pentru a adăuga la albumele distribuite",
"join": "Alăturare"
"join": "Alăturare",
"cLTitle1": "Imagini similare",
"cLDesc1": "Introducem un nou sistem bazat pe ML pentru detectarea imaginilor similare, cu care vă puteți curăța biblioteca. Disponibil în Setări->Backup->Eliberați Spațiu",
"cLTitle2": "Îmbunătățiri streaming video",
"cLDesc2": "Acum puteți declanșa manual generarea fluxului pentru videoclipuri direct din aplicație. Am adăugat, de asemenea, un nou ecran de setări pentru streaming video care vă va arăta ce procent din videoclipurile dvs. au fost procesate pentru streaming",
"cLTitle3": "Îmbunătățiri de Performanță",
"cLDesc3": "Multiple îmbunătățiri în fundal, inclusiv o utilizare mai bună a cache-ului și o experiență de defilare mai fluidă"
}

View File

@@ -1785,5 +1785,11 @@
"analysis": "Анализ",
"day": "День",
"filter": "Фильтр",
"font": "Шрифт"
"font": "Шрифт",
"cLTitle1": "Похожие изображения",
"cLDesc1": "Мы внедряем новую систему на основе ML для обнаружения похожих изображений, с помощью которой вы можете очистить свою библиотеку. Доступно в Настройки->Резервная копия->Освободить место",
"cLTitle2": "Улучшения видео стриминга",
"cLDesc2": "Теперь вы можете вручную запустить генерацию потока для видео прямо из приложения. Мы также добавили новый экран настроек видео стриминга, который покажет вам, какой процент ваших видео был обработан для стриминга",
"cLTitle3": "Улучшения производительности",
"cLDesc3": "Множественные улучшения под капотом, включая лучшее использование кэша и более плавную прокрутку"
}

View File

@@ -1776,5 +1776,11 @@
"same": "Aynı",
"different": "Farklı",
"sameperson": "Aynı kişi mi?",
"indexingPausedStatusDescription": "Dizin oluşturma duraklatıldı. Cihaz hazır olduğunda otomatik olarak devam edecektir. Cihaz, pil seviyesi, pil sağlığı ve termal durumu sağlıklı bir aralıkta olduğunda hazır kabul edilir."
"indexingPausedStatusDescription": "Dizin oluşturma duraklatıldı. Cihaz hazır olduğunda otomatik olarak devam edecektir. Cihaz, pil seviyesi, pil sağlığı ve termal durumu sağlıklı bir aralıkta olduğunda hazır kabul edilir.",
"cLTitle1": "Benzer görüntüler",
"cLDesc1": "Benzer görüntüleri tespit etmek için yeni bir ML tabanlı sistem tanıtıyoruz, bununla kütüphanenizi temizleyebilirsiniz. Ayarlar -> Yedekleme -> Alan boşalt kısmından ulaşabilirsiniz",
"cLTitle2": "Video akış geliştirmeleri",
"cLDesc2": "Artık doğrudan uygulamadan videolar için akış oluşturmayı manuel olarak tetikleyebilirsiniz. Ayrıca videolarınızın yüzde kaçının akış için işlendiğini gösteren yeni bir video akış ayarları ekranı da ekledik",
"cLTitle3": "Performans İyileştirmeleri",
"cLDesc3": "Daha iyi önbellek kullanımı ve daha pürüzsüz kaydırma deneyimi dahil olmak üzere perde arkasında birçok iyileştirme"
}

View File

@@ -1509,5 +1509,11 @@
},
"legacyInvite": "{email} запросив вас стати довіреною особою",
"authToManageLegacy": "Авторизуйтесь, щоби керувати довіреними контактами",
"useDifferentPlayerInfo": "Виникли проблеми з відтворенням цього відео? Натисніть і утримуйте тут, щоб спробувати інший плеєр."
"useDifferentPlayerInfo": "Виникли проблеми з відтворенням цього відео? Натисніть і утримуйте тут, щоб спробувати інший плеєр.",
"cLTitle1": "Схожі зображення",
"cLDesc1": "Ми впроваджуємо нову систему на основі ML для виявлення схожих зображень, за допомогою якої ви можете очистити свою бібліотеку. Доступно в Налаштування->Резервна копія->Звільнити місце",
"cLTitle2": "Покращення відео стрімінгу",
"cLDesc2": "Тепер ви можете вручну запустити генерацію потоку для відео прямо з додатку. Ми також додали новий екран налаштувань відео стрімінгу, який покаже вам, який відсоток ваших відео було оброблено для стрімінгу",
"cLTitle3": "Покращення продуктивності",
"cLDesc3": "Численні покращення під капотом, включаючи краще використання кешу та більш плавну прокрутку"
}

View File

@@ -1776,14 +1776,6 @@
"same": "Chính xác",
"different": "Khác",
"sameperson": "Cùng một người?",
"cLTitle1": "Trình chỉnh sửa ảnh nâng cao",
"cLDesc1": "Chúng tôi phát hành một trình chỉnh sửa ảnh tân tiến, bổ sung thêm cắt ảnh, bộ lọc có sẵn để chỉnh sửa nhanh, các tùy chọn tinh chỉnh bao gồm độ bão hòa, độ tương phản, độ sáng, độ ấm và nhiều hơn nữa. Trình chỉnh sửa mới cũng bao gồm khả năng vẽ lên ảnh và thêm emoji dưới dạng nhãn dán.",
"cLTitle2": "Album thông minh",
"cLDesc2": "Giờ đây, bạn có thể tự động thêm ảnh của những người đã chọn vào bất kỳ album nào. Chỉ cần mở album và chọn \"Tự động thêm người\" trong menu. Nếu sử dụng cùng với album chia sẻ, bạn có thể chia sẻ ảnh mà không cần tốn công.",
"cLTitle3": "Cải tiến Thư viện ảnh",
"cLDesc3": "Chúng tôi bổ sung tính năng phân nhóm thư viện ảnh theo tuần, tháng và năm. Giờ đây, bạn có thể tùy chỉnh thư viện ảnh theo đúng ý muốn với các tùy chọn mới này, cùng với các lưới tùy chỉnh.",
"cLTitle4": "Cuộn nhanh hơn",
"cLDesc4": "Cùng với một loạt cải tiến ngầm nhằm nâng cao trải nghiệm cuộn thư viện, chúng tôi cũng đã thiết kế lại thanh cuộn để hiển thị các điểm đánh dấu, cho phép bạn nhanh chóng nhảy cóc trên dòng thời gian.",
"indexingPausedStatusDescription": "Lập chỉ mục bị tạm dừng. Nó sẽ tự động tiếp tục khi thiết bị đã sẵn sàng. Thiết bị được coi là sẵn sàng khi mức pin, tình trạng pin và trạng thái nhiệt độ nằm trong phạm vi tốt.",
"thisWeek": "Tuần này",
"lastWeek": "Tuần trước",
@@ -1827,5 +1819,123 @@
"type": "int"
}
}
}
}
},
"videosProcessed": "Video đã được xử lý",
"totalVideos": "Tổng số video",
"skippedVideos": "Video bị bỏ qua",
"videoStreamingDescriptionLine1": "Phát video trên bất kỳ thiết bị.",
"videoStreamingDescriptionLine2": "Bật để xử lý luồng phát video trên thiết bị này.",
"videoStreamingNote": "Thiết bị này chỉ xử lý các video từ 60 ngày trở xuống và có thời lượng dưới 1 phút. Đối với các video cũ hơn/dài hơn, hãy bật tính năng phát trực tuyến trong ứng dụng máy tính để bàn.",
"createStream": "Tạo phát trực tiếp",
"recreateStream": "Tạo lại phát trực tiếp",
"addedToStreamCreationQueue": "Đã thêm vào hàng đợi tạo luồng",
"addedToStreamRecreationQueue": "Đã thêm vào hàng đợi tạo lại luồng",
"videoPreviewAlreadyExists": "Bản xem trước video đã tồn tại",
"videoAlreadyInQueue": "Tệp video đã có trong hàng đợi",
"addedToQueue": "Đã thêm vào hàng đợi",
"creatingStream": "Đang tạo luồng",
"similarImages": "Ảnh giống nhau",
"findSimilarImages": "Tìm ảnh giống nhau",
"noSimilarImagesFound": "Không tìm thấy ảnh giống nhau",
"yourPhotosLookUnique": "Ảnh của bạn trông độc đáo",
"similarGroupsFound": "{count, plural, =1{{count} nhóm được tìm thấy} other{{count} nhóm được tìm thấy}}",
"@similarGroupsFound": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"reviewAndRemoveSimilarImages": "Xem lại và xóa ảnh giống nhau",
"deletePhotosWithSize": "Xóa {count} ảnh ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "Tùy chọn lựa chọn",
"selectExactWithCount": "Giống nhau hoàn toàn ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "Chọn chính xác",
"selectSimilarWithCount": "Giống nhau một phần ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "Chọn giống nhau",
"selectAllWithCount": "Tất cả giống nhau ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "Chọn những ảnh giống nhau",
"chooseSimilarImagesToSelect": "Chọn ảnh dựa trên sự tương đồng thị giác",
"clearSelection": "Bỏ chọn",
"similarImagesCount": "{count} ảnh giống nhau",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "Xóa ({count})",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "Xóa các tệp",
"areYouSureDeleteFiles": "Bạn có chắc muốn xóa các tệp này?",
"greatJob": "Tốt lắm!",
"cleanedUpSimilarImages": "Bạn tiết kiệm được {size}",
"@cleanedUpSimilarImages": {
"placeholders": {
"size": {
"type": "String"
}
}
},
"size": "Dung lượng",
"similarity": "Sự giống nhau",
"analyzingPhotosLocally": "Phân tích ảnh của bạn trên thiết bị...",
"lookingForVisualSimilarities": "Tìm theo sự tương đồng thị giác...",
"comparingImageDetails": "So sánh các đặc điểm ảnh...",
"findingSimilarImages": "Tìm các ảnh giống nhau...",
"almostDone": "Sắp xong...",
"processingLocally": "Đang xử lý cục bộ",
"useMLToFindSimilarImages": "Xem lại và xóa những ảnh có vẻ giống nhau.",
"all": "Tất cả",
"similar": "Giống nhau",
"identical": "Giống hệt nhau",
"nothingHereTryAnotherFilter": "Không thấy gì, hãy thử thay đổi bộ lọc! 👀",
"related": "Có liên quan",
"hoorayyyy": "Hoorayyyy!",
"nothingToTidyUpHere": "Ở đây đã ngon lành rồi",
"deletingDash": "Đang xóa - ",
"cLTitle1": "Hình ảnh tương tự",
"cLDesc1": "Chúng tôi đang giới thiệu một hệ thống dựa trên ML mới để phát hiện hình ảnh tương tự, bạn có thể dùng để dọn dẹp thư viện của mình. Có sẵn trong Cài đặt -> Sao lưu -> Giải phóng dung lượng",
"cLTitle2": "Cải thiện streaming video",
"cLDesc2": "Bây giờ bạn có thể kích hoạt tạo luồng cho video trực tiếp từ ứng dụng. Chúng tôi cũng đã thêm màn hình cài đặt phát trực tuyến video mới sẽ cho bạn biết bao nhiêu phần trăm video của bạn đã được xử lý để phát trực tuyến",
"cLTitle3": "Cải Thiện Hiệu Suất",
"cLDesc3": "Nhiều cải thiện bên trong, bao gồm sử dụng bộ nhớ đệm tốt hơn và trải nghiệm cuộn mượt mà hơn"
}

View File

@@ -1776,14 +1776,6 @@
"same": "相同",
"different": "不同",
"sameperson": "是同一个人?",
"cLTitle1": "高级图像编辑器",
"cLDesc1": "我们正在发布一款全新且高级的图像编辑器,新增更多裁剪框架、快速编辑的滤镜预设,以及包括饱和度、对比度、亮度、色温等在内的精细调整选项。新的编辑器还支持在照片上绘制和添加表情符号作为贴纸。",
"cLTitle2": "智能相册",
"cLDesc2": "您现在可以将所选人物的照片自动添加到任何相册。只需进入相册,从溢出菜单中选择“自动添加人物”。如果与共享相册一起使用,您可以零点击分享照片。",
"cLTitle3": "改进的相册",
"cLDesc3": "我们新增了按周、月、年对图库进行分组的功能。您现在可以通过这些新的分组选项以及自定义网格,定制图库的外观,完全按照您的喜好进行设置",
"cLTitle4": "更快滚动",
"cLDesc4": "除了多项后台改进以提升图库滚动体验外,我们还重新设计了滚动条,添加了标记功能,让您可以快速跳转到时间轴上的不同位置。",
"indexingPausedStatusDescription": "索引已暂停。待设备准备就绪后,索引将自动恢复。当设备的电池电量、电池健康度和温度状态处于健康范围内时,设备即被视为准备就绪。",
"thisWeek": "本周",
"lastWeek": "上周",
@@ -1827,5 +1819,117 @@
"type": "int"
}
}
}
}
},
"videosProcessed": "视频已处理",
"totalVideos": "视频总数",
"skippedVideos": "已跳过的视频",
"videoStreamingDescriptionLine1": "在任何设备上立即播放视频。",
"videoStreamingDescriptionLine2": "启用以处理此设备上的视频流。",
"videoStreamingNote": "此设备仅处理过去 60 天内时长不超过 1 分钟的视频。对于更早/更长的视频,请在桌面应用中启用流式传输。",
"createStream": "创建流",
"recreateStream": "重建流",
"addedToStreamCreationQueue": "已添加到流创建队列",
"addedToStreamRecreationQueue": "已添加到流重建队列",
"videoPreviewAlreadyExists": "视频预览已存在",
"videoAlreadyInQueue": "视频文件已存在于队列中",
"addedToQueue": "已添加至队列",
"creatingStream": "正在创建流",
"similarImages": "相似图片",
"findSimilarImages": "查找相似图片",
"noSimilarImagesFound": "未找到相似图片",
"yourPhotosLookUnique": "您的照片看起来很独特",
"similarGroupsFound": "{count, plural, =1{{count} 组已找到} other{{count} 组已找到}}",
"@similarGroupsFound": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"reviewAndRemoveSimilarImages": "查看并删除相似图片",
"deletePhotosWithSize": "删除 {count} 张照片 ({size})",
"@deletePhotosWithSize": {
"placeholders": {
"count": {
"type": "String"
},
"size": {
"type": "String"
}
}
},
"selectionOptions": "选择选项",
"selectExactWithCount": "完全相似 ({count})",
"@selectExactWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectExact": "精确选择",
"selectSimilarWithCount": "部分相似 ({count})",
"@selectSimilarWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilar": "选择相似项",
"selectAllWithCount": "所有相似项 ({count})",
"@selectAllWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"selectSimilarImagesTitle": "选择所有相似图片",
"chooseSimilarImagesToSelect": "根据视觉相似性选择图像",
"clearSelection": "清除选择",
"similarImagesCount": "{count} 张相似图片",
"@similarImagesCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteWithCount": "删除 ({count}) 项",
"@deleteWithCount": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"deleteFiles": "删除文件",
"areYouSureDeleteFiles": "您确定要删除这些文件吗?",
"greatJob": "做得好!",
"cleanedUpSimilarImages": "您已释放了 {size} 的空间",
"@cleanedUpSimilarImages": {
"placeholders": {
"size": {
"type": "String"
}
}
},
"size": "大小",
"similarity": "相似度",
"processingLocally": "正在本地处理",
"useMLToFindSimilarImages": "审查并删除看起来彼此相似的图像。",
"all": "全部",
"similar": "相似的",
"identical": "完全相同",
"nothingHereTryAnotherFilter": "此处无内容,请尝试其他过滤器!👀",
"related": "相关",
"hoorayyyy": "耶~~!",
"nothingToTidyUpHere": "这里没什么可清理的",
"cLTitle1": "相似图像",
"cLDesc1": "我们正在推出一个基于机器学习的新系统来检测相似图像,您可以用它来清理您的图库。在 设置 -> 备份 -> 释放空间 中可用",
"cLTitle2": "视频流媒体增强",
"cLDesc2": "您现在可以直接从应用程序手动触发视频的流生成。我们还添加了一个新的视频流设置屏幕,它将显示您的视频中有百分之几已被处理用于流媒体播放",
"cLTitle3": "性能改进",
"cLDesc3": "多个底层改进,包括更好的缓存使用和更流畅的滚动体验"
}

View File

@@ -2,6 +2,29 @@ import "package:flutter/widgets.dart";
import 'package:photos/generated/intl/app_localizations.dart';
import "package:shared_preferences/shared_preferences.dart";
// list of locales which are enabled for photos app.
// Add more language to the list only when at least 90% of the strings are
// translated in the corresponding language.
const List<Locale> appSupportedLocales = <Locale>[
Locale('en'),
Locale('es'),
Locale('de'),
Locale('fr'),
Locale('it'),
Locale('ja'),
Locale("nl"),
Locale("no"),
Locale("pl"),
Locale("pt", "BR"),
Locale('pt', 'PT'),
Locale("ro"),
Locale("ru"),
Locale("tr"),
Locale("uk"),
Locale("vi"),
Locale("zh", "CN"),
];
extension AppLocalizationsX on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this);
}
@@ -12,12 +35,12 @@ Locale? autoDetectedLocale;
Locale localResolutionCallBack(deviceLocales, supportedLocales) {
_onDeviceLocales = deviceLocales;
final Set<String> languageSupport = {};
for (Locale supportedLocale in AppLocalizations.supportedLocales) {
for (Locale supportedLocale in appSupportedLocales) {
languageSupport.add(supportedLocale.languageCode);
}
for (Locale locale in deviceLocales) {
// check if exact local is supported, if yes, return it
if (AppLocalizations.supportedLocales.contains(locale)) {
if (appSupportedLocales.contains(locale)) {
autoDetectedLocale = locale;
return locale;
}
@@ -67,7 +90,7 @@ Future<Locale?> getLocale({
} else {
savedLocale = Locale(savedValue);
}
if (AppLocalizations.supportedLocales.contains(savedLocale)) {
if (appSupportedLocales.contains(savedLocale)) {
return savedLocale;
}
}
@@ -81,7 +104,7 @@ Future<Locale?> getLocale({
}
Future<void> setLocale(Locale locale) async {
if (!AppLocalizations.supportedLocales.contains(locale)) {
if (!appSupportedLocales.contains(locale)) {
throw Exception('Locale $locale is not supported by the app');
}
final StringBuffer out = StringBuffer(locale.languageCode);

View File

@@ -1,10 +1,10 @@
import 'dart:async';
import "dart:core";
import 'dart:io';
import "package:adaptive_theme/adaptive_theme.dart";
import "package:computer/computer.dart";
import 'package:ente_crypto/ente_crypto.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import "package:flutter/rendering.dart";
@@ -36,7 +36,6 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
import 'package:photos/services/machine_learning/ml_service.dart';
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
import "package:photos/services/notification_service.dart";
import 'package:photos/services/push_service.dart';
import 'package:photos/services/search_service.dart';
import 'package:photos/services/sync/local_sync_service.dart';
import 'package:photos/services/sync/remote_sync_service.dart';
@@ -273,11 +272,12 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
}
if (Platform.isIOS) {
PushService.instance.init().then((_) {
FirebaseMessaging.onBackgroundMessage(
_firebaseMessagingBackgroundHandler,
);
}).ignore();
// ignore: unawaited_futures
// PushService.instance.init().then((_) {
// FirebaseMessaging.onBackgroundMessage(
// _firebaseMessagingBackgroundHandler,
// );
// });
}
_logger.info("PushService/HomeWidget done $tlog");
unawaited(SemanticSearchService.instance.init());
@@ -402,31 +402,6 @@ Future<bool> _isRunningInForeground() async {
(currentTime - kFGTaskDeathTimeoutInMicroseconds);
}
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
final bool isRunningInFG = await _isRunningInForeground(); // hb
final bool isInForeground = AppLifecycleService.instance.isForeground;
if (await _isRunningInForeground()) {
_logger.info(
"Background push received when app is alive and runningInFS: $isRunningInFG inForeground: $isInForeground",
);
if (PushService.shouldSync(message)) {
await _sync('firebaseBgSyncActiveProcess');
}
} else {
// App is dead
runWithLogs(
() async {
_logger.info("Background push received");
await _init(true, via: 'firebasePush');
if (PushService.shouldSync(message)) {
await _sync('firebaseBgSyncNoActiveProcess');
}
},
prefix: "[fbg]",
).ignore();
}
}
Future<void> _logFGHeartBeatInfo(SharedPreferences prefs) async {
final bool isRunningInFG = await _isRunningInForeground();
await prefs.reload();

View File

@@ -13,7 +13,8 @@ class Collection {
final User owner;
final String encryptedKey;
final String? keyDecryptionNonce;
@Deprecated("Use collectionName instead")
/// WARNING: use collectionName instead of name! Name is deprecated but can't be removed because of old accounts.
String? name;
// encryptedName & nameDecryptionNonce will be null for collections

View File

@@ -4,7 +4,6 @@ import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
// import 'package:flutter/foundation.dart';
// import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/errors.dart';
import "package:photos/generated/l10n.dart";
@@ -29,6 +28,7 @@ class BillingService {
late final _logger = Logger("BillingService");
final Dio _enteDio;
// ignore: unused_field
bool _isOnSubscriptionPage = false;
Future<BillingPlans>? _future;
@@ -42,23 +42,6 @@ class BillingService {
// await FlutterInappPurchase.instance.initConnection;
// FlutterInappPurchase.instance.clearTransactionIOS();
// }
InAppPurchase.instance.purchaseStream.listen((purchases) {
if (_isOnSubscriptionPage) {
return;
}
for (final purchase in purchases) {
if (purchase.status == PurchaseStatus.purchased) {
verifySubscription(
purchase.productID,
purchase.verificationData.serverVerificationData,
).then((response) {
InAppPurchase.instance.completePurchase(purchase);
});
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
InAppPurchase.instance.completePurchase(purchase);
}
}
});
}
void clearCache() {

View File

@@ -32,7 +32,14 @@ class ComputeController {
bool _isDeviceHealthy = true;
bool _isUserInteracting = true;
bool _canRunCompute = false;
/// If true, user interaction is ignored and compute tasks can run regardless of user activity.
bool interactionOverride = false;
/// If true, compute tasks are paused regardless of device health or user activity.
bool get computeBlocked => _computeBlocks.isNotEmpty;
final Set<String> _computeBlocks = {};
late Timer _userInteractionTimer;
ComputeRunState _currentRunState = ComputeRunState.idle;
@@ -42,41 +49,57 @@ class ComputeController {
ComputeController() {
_logger.info('ComputeController constructor');
init();
_logger.info('init done ');
}
// Directly assign the values + Attach listener for compute controller
Future<void> init() async {
// Interaction Timer
_startInteractionTimer(kDefaultInteractionTimeout);
// Thermal related
_onThermalStateUpdate(await _thermal.thermalStatus);
_thermal.onThermalStatusChanged.listen((ThermalStatus thermalState) {
_onThermalStateUpdate(thermalState);
});
// Battery State
if (Platform.isIOS) {
if (kDebugMode) {
_logger.info(
_logger.fine(
"iOS battery info stream is not available in simulator, disabling in debug mode",
);
// if you need to test on physical device, uncomment this check
return;
} else {
// Update Battery state for iOS
_oniOSBatteryStateUpdate(await BatteryInfoPlugin().iosBatteryInfo);
BatteryInfoPlugin()
.iosBatteryInfoStream
.listen((IosBatteryInfo? batteryInfo) {
_oniOSBatteryStateUpdate(batteryInfo);
});
}
BatteryInfoPlugin()
.iosBatteryInfoStream
.listen((IosBatteryInfo? batteryInfo) {
_oniOSBatteryStateUpdate(batteryInfo);
});
}
if (Platform.isAndroid) {
} else if (Platform.isAndroid) {
// Update Battery state for Android
_onAndroidBatteryStateUpdate(
await BatteryInfoPlugin().androidBatteryInfo,
);
BatteryInfoPlugin()
.androidBatteryInfoStream
.listen((AndroidBatteryInfo? batteryInfo) {
_onAndroidBatteryStateUpdate(batteryInfo);
});
}
_thermal.onThermalStatusChanged.listen((ThermalStatus thermalState) {
_onThermalStateUpdate(thermalState);
});
_logger.info('init done ');
}
bool requestCompute({
bool ml = false,
bool stream = false,
bool bypassInteractionCheck = false,
bool bypassMLWaiting = false,
}) {
_logger.info(
"Requesting compute: ml: $ml, stream: $stream, bypassInteraction: $bypassInteractionCheck",
"Requesting compute: ml: $ml, stream: $stream, bypassInteraction: $bypassInteractionCheck, bypassMLWaiting: $bypassMLWaiting",
);
if (!_isDeviceHealthy) {
_logger.info("Device not healthy, denying request.");
@@ -86,11 +109,15 @@ class ComputeController {
_logger.info("User interacting, denying request.");
return false;
}
if (computeBlocked) {
_logger.info("Compute is blocked by: $_computeBlocks, denying request.");
return false;
}
bool result = false;
if (ml) {
result = _requestML();
} else if (stream) {
result = _requestStream();
result = _requestStream(bypassMLWaiting);
} else {
_logger.severe("No compute request specified, denying request.");
}
@@ -117,14 +144,15 @@ class ComputeController {
return false;
}
bool _requestStream() {
if (_currentRunState == ComputeRunState.idle && !_waitingToRunML) {
bool _requestStream([bool bypassMLWaiting = false]) {
if (_currentRunState == ComputeRunState.idle &&
(bypassMLWaiting || !_waitingToRunML)) {
_logger.info("Stream request granted");
_currentRunState = ComputeRunState.generatingStream;
return true;
}
_logger.info(
"Stream request denied, current state: $_currentRunState, wants to run ML: $_waitingToRunML",
"Stream request denied, current state: $_currentRunState, wants to run ML: $_waitingToRunML, bypassMLWaiting: $bypassMLWaiting",
);
return false;
}
@@ -165,12 +193,25 @@ class ComputeController {
_fireControlEvent();
}
void blockCompute({required String blocker}) {
_computeBlocks.add(blocker);
_logger.info("Forcing to pauze compute due to: $blocker");
_fireControlEvent();
}
void unblockCompute({required String blocker}) {
_computeBlocks.remove(blocker);
_logger.info("removed blocker: $blocker, now blocked: $computeBlocked");
_fireControlEvent();
}
void _fireControlEvent() {
final shouldRunCompute = _isDeviceHealthy && _canRunGivenUserInteraction();
final shouldRunCompute =
_isDeviceHealthy && _canRunGivenUserInteraction() && !computeBlocked;
if (shouldRunCompute != _canRunCompute) {
_canRunCompute = shouldRunCompute;
_logger.info(
"Firing event: $shouldRunCompute (device health: $_isDeviceHealthy, user interaction: $_isUserInteracting, mlInteractionOverride: $interactionOverride)",
"Firing event: $shouldRunCompute (device health: $_isDeviceHealthy, user interaction: $_isUserInteracting, mlInteractionOverride: $interactionOverride, blockers: $_computeBlocks)",
);
Bus.instance.fire(ComputeControlEvent(shouldRunCompute));
}

View File

@@ -1,3 +1,4 @@
import "dart:io" show File;
import "dart:math" show max;
import "package:flutter/foundation.dart" show kDebugMode;
@@ -10,6 +11,7 @@ import "package:photos/extensions/stop_watch.dart";
import "package:photos/models/file/extensions/file_props.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/models/similar_files.dart";
import "package:photos/services/favorites_service.dart";
import "package:photos/services/machine_learning/ml_computer.dart";
import "package:photos/services/machine_learning/ml_result.dart";
import "package:photos/services/search_service.dart";
@@ -257,6 +259,11 @@ class SimilarImagesService {
group.addFile(newFile);
group.furthestDistance = max(group.furthestDistance, distance);
group.files.sort((a, b) {
if (FavoritesService.instance.isFavoriteCache(a)) {
return -1;
} else if (FavoritesService.instance.isFavoriteCache(b)) {
return 1;
}
final sizeComparison =
(b.fileSize ?? 0).compareTo(a.fileSize ?? 0);
if (sizeComparison != 0) return sizeComparison;
@@ -307,6 +314,11 @@ class SimilarImagesService {
similarNewFiles.add(newFile);
alreadyUsedNewFiles.add(newFileID);
similarNewFiles.sort((a, b) {
if (FavoritesService.instance.isFavoriteCache(a)) {
return -1;
} else if (FavoritesService.instance.isFavoriteCache(b)) {
return 1;
}
final sizeComparison = (b.fileSize ?? 0).compareTo(a.fileSize ?? 0);
if (sizeComparison != 0) return sizeComparison;
return a.displayName.compareTo(b.displayName);
@@ -381,6 +393,11 @@ class SimilarImagesService {
}
// show highest quality files first
similarFilesList.sort((a, b) {
if (FavoritesService.instance.isFavoriteCache(a)) {
return -1;
} else if (FavoritesService.instance.isFavoriteCache(b)) {
return 1;
}
final sizeComparison = (b.fileSize ?? 0).compareTo(a.fileSize ?? 0);
if (sizeComparison != 0) return sizeComparison;
return a.displayName.compareTo(b.displayName);
@@ -434,6 +451,20 @@ class SimilarImagesService {
);
return cache;
}
Future<void> clearCache() async {
try {
final cachePath = await _getCachePath();
final file = File(cachePath);
if (await file.exists()) {
await file.delete();
_logger.info("Cleared similar files cache at $cachePath");
}
} catch (e, s) {
_logger.severe("Error clearing similar files cache", e, s);
rethrow;
}
}
}
bool setsAreEqual(Set<String> set1, Set<String> set2) {

View File

@@ -14,7 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart';
class UpdateService {
static const kUpdateAvailableShownTimeKey = "update_available_shown_time_key";
static const changeLogVersionKey = "update_change_log_key";
static const currentChangeLogVersion = 31;
static const currentChangeLogVersion = 36;
LatestVersionInfo? _latestVersion;
final _logger = Logger("UpdateService");

View File

@@ -125,6 +125,10 @@ class VideoPreviewService {
file.uploadedFileID!,
);
if (alreadyInQueue) {
// File is already queued, but trigger processing in case it was stalled
if (uploadingFileId < 0) {
queueFiles(duration: Duration.zero, isManual: true, forceProcess: true);
}
return false; // Indicates file was already in queue
}
@@ -153,7 +157,25 @@ class VideoPreviewService {
bool isCurrentlyProcessing(int? uploadedFileID) {
if (uploadedFileID == null) return false;
return uploadingFileId == uploadedFileID;
// Also check if file is in queue or other processing states
final item = _items[uploadedFileID];
if (item != null) {
switch (item.status) {
case PreviewItemStatus.inQueue:
case PreviewItemStatus.compressing:
case PreviewItemStatus.uploading:
return true;
default:
return false;
}
}
return false;
}
PreviewItemStatus? getProcessingStatus(int uploadedFileID) {
return _items[uploadedFileID]?.status;
}
Future<bool> _isRecreateOperation(EnteFile file) async {
@@ -252,15 +274,20 @@ class VideoPreviewService {
Future<void> chunkAndUploadVideo(
BuildContext? ctx,
EnteFile enteFile, [
EnteFile enteFile, {
/// Indicates this function is an continuation of a chunking thread
bool continuation = false,
// not used currently
bool forceUpload = false,
bool isManual = false,
]) async {
}) async {
final bool isManual =
await uploadLocksDB.isInStreamQueue(enteFile.uploadedFileID!);
final canStream = _isPermissionGranted();
if (!canStream) {
_logger.info(
"Pause preview due to disabledSteaming($isVideoStreamingEnabled) or computeController permission) - isManual: $isManual",
);
computeController.releaseCompute(stream: true);
if (isVideoStreamingEnabled) _logger.info("No permission to run compute");
clearQueue();
return;
@@ -307,7 +334,7 @@ class VideoPreviewService {
}
// check if there is already a preview in processing
if (uploadingFileId >= 0) {
if (!continuation && uploadingFileId >= 0) {
if (uploadingFileId == enteFile.uploadedFileID) return;
_items[enteFile.uploadedFileID!] = PreviewItem(
@@ -558,21 +585,26 @@ class VideoPreviewService {
_removeFile(enteFile);
_removeFromLocks(enteFile).ignore();
}
// reset uploading status if this was getting processed
if (uploadingFileId == enteFile.uploadedFileID!) {
uploadingFileId = -1;
}
_logger.info(
"[chunk] Processing ${_items.length} items for streaming, $error",
);
// process next file
if (fileQueue.isNotEmpty) {
// process next file
_logger.info(
"[chunk] Processing ${_items.length} items for streaming, $error",
);
final entry = fileQueue.entries.first;
final file = entry.value;
fileQueue.remove(entry.key);
await chunkAndUploadVideo(ctx, file);
await chunkAndUploadVideo(
ctx,
file,
continuation: true,
);
} else {
_logger.info(
"[chunk] Nothing to process releasing compute, $error",
);
computeController.releaseCompute(stream: true);
uploadingFileId = -1;
}
}
}
@@ -983,8 +1015,9 @@ class VideoPreviewService {
}
// generate stream for all files after cutoff date
Future<void> _putFilesForPreviewCreation() async {
if (!isVideoStreamingEnabled || !await canUseHighBandwidth()) return;
// returns false if it fails to launch chuncking function
Future<bool> _putFilesForPreviewCreation() async {
if (!isVideoStreamingEnabled || !await canUseHighBandwidth()) return false;
Map<int, String> failureFiles = {};
Map<int, String> manualQueueFiles = {};
@@ -1120,7 +1153,7 @@ class VideoPreviewService {
final totalFiles = fileQueue.length;
if (totalFiles == 0) {
_logger.info("[init] No preview to cache");
return;
return false;
}
_logger.info(
@@ -1132,6 +1165,7 @@ class VideoPreviewService {
final file = entry.value;
fileQueue.remove(entry.key);
chunkAndUploadVideo(null, file).ignore();
return true;
}
bool _allowStream() {
@@ -1144,26 +1178,34 @@ class VideoPreviewService {
computeController.requestCompute(
stream: true,
bypassInteractionCheck: true,
bypassMLWaiting: true,
);
}
/// To check if it's enabled, device is healthy and running streaming
bool _isPermissionGranted() {
return isVideoStreamingEnabled &&
computeController.computeState == ComputeRunState.generatingStream;
computeController.computeState == ComputeRunState.generatingStream &&
computeController.isDeviceHealthy;
}
void queueFiles({
Duration duration = const Duration(seconds: 5),
bool isManual = false,
bool forceProcess = false,
}) {
Future.delayed(duration, () async {
if (_hasQueuedFile) return;
if (_hasQueuedFile && !forceProcess) return;
final isStreamAllowed = isManual ? _allowManualStream() : _allowStream();
if (!isStreamAllowed) return;
await _ensurePreviewIdsInitialized();
await _putFilesForPreviewCreation();
final result = await _putFilesForPreviewCreation();
// Cannot proceed to stream generation, would have to release compute ASAP
if (!result) {
computeController.releaseCompute(stream: true);
}
});
}
}

View File

@@ -276,12 +276,12 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
isDisabled: _selectedCollections.isEmpty,
onTap: () async {
if (widget.selectedPeople != null) {
final ProgressDialog? dialog = createProgressDialog(
final ProgressDialog dialog = createProgressDialog(
context,
AppLocalizations.of(context).uploadingFilesToAlbum,
isDismissible: true,
);
await dialog?.show();
await dialog.show();
for (final collection in _selectedCollections) {
try {
await smartAlbumsService.addPeopleToSmartAlbum(
@@ -297,7 +297,7 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
}
}
unawaited(smartAlbumsService.syncSmartAlbums());
await dialog?.hide();
await dialog.hide();
return;
}
final CollectionActions collectionActions =

View File

@@ -35,9 +35,8 @@ class TextInputWidget extends StatefulWidget {
final bool popNavAfterSubmission;
final bool shouldSurfaceExecutionStates;
final TextCapitalization? textCapitalization;
@Deprecated(
"Do not use this widget for password input. Create a separate PasswordInputWidget. This widget is becoming bloated and hard to maintain, so will create a PasswordInputWidget and remove this field from this widget in future",
)
/// WARNING: Do not use this widget for password input. Create a separate PasswordInputWidget. This widget is becoming bloated and hard to maintain, so will create a PasswordInputWidget and remove this field from this widget in future
final bool isPasswordInput;
///Clear comes in the form of a suffix icon. It is unrelated to onCancel.

View File

@@ -94,7 +94,7 @@ class _LandingPageWidgetState extends State<LandingPageWidget> {
routeToPage(
context,
LanguageSelectorPage(
AppLocalizations.supportedLocales,
appSupportedLocales,
(locale) async {
await setLocale(locale);
EnteApp.setLocale(context, locale);

View File

@@ -77,7 +77,7 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
ButtonWidget(
buttonType: ButtonType.trailingIconSecondary,
buttonSize: ButtonSize.large,
labelText: AppLocalizations.of(context).rateTheApp,
labelText: AppLocalizations.of(context).rateUs,
icon: Icons.favorite_rounded,
iconColor: enteColorScheme.primary500,
onTap: () async {
@@ -112,12 +112,7 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
context.l10n.cLTitle3,
context.l10n.cLDesc3,
),
ChangeLogEntry(
context.l10n.cLTitle4,
context.l10n.cLDesc4,
),
]);
return Container(
padding: const EdgeInsets.only(left: 16),
child: Scrollbar(

View File

@@ -1,24 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:photos/core/configuration.dart';
import "package:photos/service_locator.dart";
import "package:photos/ui/payment/store_subscription_page.dart";
import 'package:photos/ui/payment/stripe_subscription_page.dart';
StatefulWidget getSubscriptionPage({bool isOnBoarding = false}) {
if (updateService.isIndependentFlavor()) {
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
}
if (flagService.enableStripe && _isUserCreatedPostStripeSupport()) {
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
} else {
return StoreSubscriptionPage(isOnboarding: isOnBoarding);
}
}
// return true if the user was created after we added support for stripe payment
// on frame. We do this check to avoid showing Stripe payment option for earlier
// users who might have paid via playStore. This method should be removed once
// we have better handling for active play/app store subscription & stripe plans.
bool _isUserCreatedPostStripeSupport() {
return Configuration.instance.getUserID()! > 1580559962386460;
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
}

View File

@@ -251,9 +251,12 @@ class _FreeUpSpaceOptionsScreenState extends State<FreeUpSpaceOptionsScreen> {
);
},
),
MenuSectionDescriptionWidget(
content: AppLocalizations.of(context)
.viewLargeFilesDesc,
Align(
alignment: Alignment.centerLeft,
child: MenuSectionDescriptionWidget(
content: AppLocalizations.of(context)
.viewLargeFilesDesc,
),
),
const SizedBox(
height: 24,

View File

@@ -9,12 +9,20 @@ import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import 'package:photos/ui/components/toggle_switch_widget.dart';
import 'package:photos/ui/notification/toast.dart';
import 'package:photos/ui/settings/common_settings.dart';
import 'package:photos/ui/settings/debug/local_thumbnail_config_screen.dart';
import 'package:photos/utils/navigation_util.dart';
class DebugSectionWidget extends StatelessWidget {
class DebugSectionWidget extends StatefulWidget {
const DebugSectionWidget({super.key});
@override
State<DebugSectionWidget> createState() => _DebugSectionWidgetState();
}
class _DebugSectionWidgetState extends State<DebugSectionWidget> {
@override
Widget build(BuildContext context) {
return ExpandableMenuItemWidget(
@@ -27,6 +35,43 @@ class DebugSectionWidget extends StatelessWidget {
Widget _getSectionOptions(BuildContext context) {
return Column(
children: [
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Show local ID over thumbnails",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => localSettings.showLocalIDOverThumbnails,
onChanged: () async {
await localSettings.setShowLocalIDOverThumbnails(
!localSettings.showLocalIDOverThumbnails,
);
setState(() {});
showShortToast(
context,
localSettings.showLocalIDOverThumbnails
? "Local IDs will be shown. Restart app."
: "Local IDs hidden. Restart app.",
);
},
),
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Local thumbnail queue config",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
await routeToPage(
context,
const LocalThumbnailConfigScreen(),
);
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(

View File

@@ -0,0 +1,350 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:photos/service_locator.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/title_bar_title_widget.dart';
import 'package:photos/ui/notification/toast.dart';
class LocalThumbnailConfigScreen extends StatefulWidget {
const LocalThumbnailConfigScreen({super.key});
@override
State<LocalThumbnailConfigScreen> createState() =>
_LocalThumbnailConfigScreenState();
}
class _LocalThumbnailConfigScreenState
extends State<LocalThumbnailConfigScreen> {
static final Logger _logger = Logger("LocalThumbnailConfigScreen");
late TextEditingController _smallMaxConcurrentController;
late TextEditingController _smallTimeoutController;
late TextEditingController _smallMaxSizeController;
late TextEditingController _largeMaxConcurrentController;
late TextEditingController _largeTimeoutController;
late TextEditingController _largeMaxSizeController;
@override
void initState() {
super.initState();
_initControllers();
}
void _initControllers() {
_smallMaxConcurrentController = TextEditingController(
text: localSettings.smallQueueMaxConcurrent.toString(),
);
_smallTimeoutController = TextEditingController(
text: localSettings.smallQueueTimeoutSeconds.toString(),
);
_smallMaxSizeController = TextEditingController(
text: localSettings.smallQueueMaxSize.toString(),
);
_largeMaxConcurrentController = TextEditingController(
text: localSettings.largeQueueMaxConcurrent.toString(),
);
_largeTimeoutController = TextEditingController(
text: localSettings.largeQueueTimeoutSeconds.toString(),
);
_largeMaxSizeController = TextEditingController(
text: localSettings.largeQueueMaxSize.toString(),
);
}
@override
void dispose() {
_smallMaxConcurrentController.dispose();
_smallTimeoutController.dispose();
_smallMaxSizeController.dispose();
_largeMaxConcurrentController.dispose();
_largeTimeoutController.dispose();
_largeMaxSizeController.dispose();
super.dispose();
}
Future<void> _saveSettings() async {
try {
// Validate and save small queue settings
final smallMaxConcurrent =
int.tryParse(_smallMaxConcurrentController.text);
final smallTimeout = int.tryParse(_smallTimeoutController.text);
final smallMaxSize = int.tryParse(_smallMaxSizeController.text);
// Validate and save large queue settings
final largeMaxConcurrent =
int.tryParse(_largeMaxConcurrentController.text);
final largeTimeout = int.tryParse(_largeTimeoutController.text);
final largeMaxSize = int.tryParse(_largeMaxSizeController.text);
if (smallMaxConcurrent == null ||
smallTimeout == null ||
smallMaxSize == null ||
largeMaxConcurrent == null ||
largeTimeout == null ||
largeMaxSize == null) {
showShortToast(context, "Please enter valid numbers");
return;
}
// Basic validation - just ensure positive numbers
if (smallMaxConcurrent < 1 ||
largeMaxConcurrent < 1 ||
smallTimeout < 1 ||
largeTimeout < 1 ||
smallMaxSize < 1 ||
largeMaxSize < 1) {
showShortToast(
context,
"All values must be positive numbers",
);
return;
}
await localSettings.setSmallQueueMaxConcurrent(smallMaxConcurrent);
await localSettings.setSmallQueueTimeout(smallTimeout);
await localSettings.setSmallQueueMaxSize(smallMaxSize);
await localSettings.setLargeQueueMaxConcurrent(largeMaxConcurrent);
await localSettings.setLargeQueueTimeout(largeTimeout);
await localSettings.setLargeQueueMaxSize(largeMaxSize);
_logger.info(
"Local thumbnail queue settings updated:\n"
"Small Queue - MaxConcurrent: $smallMaxConcurrent, Timeout: ${smallTimeout}s, MaxSize: $smallMaxSize\n"
"Large Queue - MaxConcurrent: $largeMaxConcurrent, Timeout: ${largeTimeout}s, MaxSize: $largeMaxSize",
);
if (mounted) {
showShortToast(
context,
"Settings saved. Restart app to apply changes.",
);
}
} catch (e) {
showShortToast(context, "Error saving settings");
}
}
Future<void> _resetToDefaults() async {
await localSettings.resetThumbnailQueueSettings();
setState(() {
_smallMaxConcurrentController.text = "15";
_smallTimeoutController.text = "60";
_smallMaxSizeController.text = "200";
_largeMaxConcurrentController.text = "5";
_largeTimeoutController.text = "60";
_largeMaxSizeController.text = "200";
});
_logger.info(
"Local thumbnail queue settings reset to defaults:\n"
"Small Queue - MaxConcurrent: 15, Timeout: 60s, MaxSize: 200\n"
"Large Queue - MaxConcurrent: 5, Timeout: 60s, MaxSize: 200",
);
if (mounted) {
showShortToast(
context,
"Reset to defaults. Restart app to apply changes.",
);
}
}
Widget _buildNumberField({
required String label,
required String hint,
required TextEditingController controller,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: getEnteTextTheme(context).body.copyWith(
color: getEnteColorScheme(context).textMuted,
),
),
const SizedBox(height: 4),
TextField(
controller: controller,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
hintText: hint,
hintStyle: getEnteTextTheme(context).body.copyWith(
color: getEnteColorScheme(context).textFaint,
),
filled: true,
fillColor: getEnteColorScheme(context).fillFaint,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
style: getEnteTextTheme(context).body,
),
],
),
);
}
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
return Scaffold(
body: Container(
color: colorScheme.backdropBase,
child: SafeArea(
child: Column(
children: [
const TitleBarTitleWidget(
title: "Local Thumbnail Queue Config",
),
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Small Local Thumbnail Queue",
style: getEnteTextTheme(context).largeBold,
),
const SizedBox(height: 8),
Text(
"Used when gallery grid has 4 or more columns",
style: getEnteTextTheme(context).small.copyWith(
color: colorScheme.textMuted,
),
),
const SizedBox(height: 16),
_buildNumberField(
label: "Max Concurrent Tasks",
hint: "Default: 15",
controller: _smallMaxConcurrentController,
),
_buildNumberField(
label: "Timeout (seconds)",
hint: "Default: 60",
controller: _smallTimeoutController,
),
_buildNumberField(
label: "Max Queue Size",
hint: "Default: 200",
controller: _smallMaxSizeController,
),
const SizedBox(height: 32),
Text(
"Large Local Thumbnail Queue",
style: getEnteTextTheme(context).largeBold,
),
const SizedBox(height: 8),
Text(
"Used when gallery grid has less than 4 columns",
style: getEnteTextTheme(context).small.copyWith(
color: colorScheme.textMuted,
),
),
const SizedBox(height: 16),
_buildNumberField(
label: "Max Concurrent Tasks",
hint: "Default: 5",
controller: _largeMaxConcurrentController,
),
_buildNumberField(
label: "Timeout (seconds)",
hint: "Default: 60",
controller: _largeTimeoutController,
),
_buildNumberField(
label: "Max Queue Size",
hint: "Default: 200",
controller: _largeMaxSizeController,
),
const SizedBox(height: 32),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _saveSettings,
style: ElevatedButton.styleFrom(
backgroundColor: colorScheme.primary700,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
"Save Settings",
style: getEnteTextTheme(context).bodyBold.copyWith(
color: Colors.white,
),
),
),
),
const SizedBox(height: 12),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: _resetToDefaults,
style: TextButton.styleFrom(
foregroundColor: colorScheme.primary700,
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: colorScheme.strokeMuted,
),
),
),
child: const Text(
"Reset to Defaults",
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
),
const SizedBox(height: 16),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: colorScheme.fillFaint,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(
Icons.info_outline,
size: 20,
color: colorScheme.textMuted,
),
const SizedBox(width: 8),
Expanded(
child: Text(
"Changes require app restart to take effect",
style: getEnteTextTheme(context).small.copyWith(
color: colorScheme.textMuted,
),
),
),
],
),
),
],
),
),
),
],
),
),
),
);
}
}

View File

@@ -22,6 +22,7 @@ import "package:photos/services/machine_learning/face_ml/person/person_service.d
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_service.dart';
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/services/machine_learning/similar_images_service.dart";
import "package:photos/services/notification_service.dart";
import "package:photos/services/search_service.dart";
import "package:photos/src/rust/api/simple.dart";
@@ -83,6 +84,25 @@ class _MLDebugSectionWidgetState extends State<MLDebugSectionWidget> {
logger.info("Building ML Debug section options");
return Column(
children: [
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Clear vectorDB index",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await ClipVectorDB.instance.deleteIndexFile(undoMigration: true);
await SimilarImagesService.instance.clearCache();
showShortToast(context, 'Deleted vectorDB index');
} catch (e, s) {
logger.severe('vectorDB index delete failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(

View File

@@ -100,7 +100,7 @@ class GeneralSectionWidget extends StatelessWidget {
await routeToPage(
context,
LanguageSelectorPage(
AppLocalizations.supportedLocales,
appSupportedLocales,
(locale) async {
await setLocale(locale);
EnteApp.setLocale(context, locale);

View File

@@ -159,16 +159,11 @@ class _ItemsWidgetState extends State<ItemsWidget> {
return 'Русский';
case 'tr':
return 'Türkçe';
case 'fi':
return 'Suomi';
case 'zh':
if (locale.countryCode == 'CN') {
return '中文 (简体)';
}
return '中文';
case 'zh-CN':
return '中文';
case 'ko':
return '한국어';
case 'ar':
return 'العربية';
case 'uk':
return 'Українська';
case 'vi':

View File

@@ -114,7 +114,7 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
darkTheme: widget.darkTheme,
locale: widget.locale,
debugShowCheckedModeBanner: false,
supportedLocales: AppLocalizations.supportedLocales,
supportedLocales: appSupportedLocales,
localeListResolutionCallback: localResolutionCallBack,
localizationsDelegates: const [
...AppLocalizations.localizationsDelegates,

View File

@@ -39,7 +39,7 @@ class CircularIconButton extends StatelessWidget {
? Theme.of(context)
.colorScheme
.imageEditorPrimaryColor
.withOpacity(0.24)
.withValues(alpha: 0.24)
: Theme.of(context).colorScheme.editorBackgroundColor,
shape: BoxShape.circle,
border: Border.all(

View File

@@ -179,7 +179,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
final textTheme = getEnteTextTheme(context);
return PopScope(
canPop: false,
onPopInvoked: (didPop) {
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
editorKey.currentState?.disablePopScope = true;
_showExitConfirmationDialog(context);
@@ -366,7 +366,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
color: isHovered
? colorScheme.warning400.withOpacity(0.8)
? colorScheme.warning400.withValues(alpha: 0.8)
: Colors.white,
shape: BoxShape.circle,
),
@@ -378,7 +378,7 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
isHovered
? Colors.white
: colorScheme.warning400
.withOpacity(0.8),
.withValues(alpha: 0.8),
BlendMode.srcIn,
),
),

View File

@@ -233,10 +233,10 @@ class _BackgroundPickerWidget extends StatelessWidget {
'backgroundColor': Theme.of(context).colorScheme.editorBackgroundColor,
'border': null,
'textColor': Colors.black,
'selectedInnerBackgroundColor': Colors.black.withOpacity(0.11),
'selectedInnerBackgroundColor': Colors.black.withValues(alpha: 0.11),
'innerBackgroundColor': isLightMode
? Colors.black.withOpacity(0.11)
: Colors.white.withOpacity(0.11),
? Colors.black.withValues(alpha: 0.11)
: Colors.white.withValues(alpha: 0.11),
},
LayerBackgroundMode.onlyColor: {
'text': 'Aa',
@@ -247,7 +247,7 @@ class _BackgroundPickerWidget extends StatelessWidget {
isLightMode ? null : Border.all(color: Colors.white, width: 2),
'textColor': Colors.black,
'selectedInnerBackgroundColor': Colors.white,
'innerBackgroundColor': Colors.white.withOpacity(0.6),
'innerBackgroundColor': Colors.white.withValues(alpha: 0.6),
},
};

View File

@@ -320,11 +320,11 @@ class _CircularProgressWithValueState extends State<CircularProgressWithValue>
decoration: BoxDecoration(
shape: BoxShape.circle,
color: showValue || widget.isSelected
? progressColor.withOpacity(0.2)
? progressColor.withValues(alpha: 0.2)
: Theme.of(context).colorScheme.editorBackgroundColor,
border: Border.all(
color: widget.isSelected
? progressColor.withOpacity(0.4)
? progressColor.withValues(alpha: 0.4)
: Theme.of(context).colorScheme.editorBackgroundColor,
width: 2,
),

View File

@@ -2,6 +2,7 @@ import "dart:async";
import "package:flutter/foundation.dart" show kDebugMode;
import 'package:flutter/material.dart';
import "package:flutter_spinkit/flutter_spinkit.dart" show SpinKitFadingCircle;
import "package:flutter_svg/svg.dart";
import "package:intl/intl.dart";
import 'package:logging/logging.dart';
@@ -13,6 +14,7 @@ import "package:photos/models/selected_files.dart";
import "package:photos/models/similar_files.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/collections_service.dart";
import "package:photos/services/favorites_service.dart";
import "package:photos/services/machine_learning/similar_images_service.dart";
import "package:photos/theme/colors.dart";
import 'package:photos/theme/ente_theme.dart';
@@ -56,7 +58,8 @@ class SimilarImagesPage extends StatefulWidget {
State<SimilarImagesPage> createState() => _SimilarImagesPageState();
}
class _SimilarImagesPageState extends State<SimilarImagesPage> {
class _SimilarImagesPageState extends State<SimilarImagesPage>
with SingleTickerProviderStateMixin {
static const crossAxisCount = 3;
static const crossAxisSpacing = 12.0;
static const double _closeThreshold = 0.02;
@@ -76,6 +79,8 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
late SelectedFiles _selectedFiles;
late ValueNotifier<String> _deleteProgress;
late ScrollController _scrollController;
late AnimationController deleteAnimationController;
List<SimilarFiles> get _filteredGroups {
final filteredGroups = <SimilarFiles>[];
@@ -110,6 +115,11 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
super.initState();
_selectedFiles = SelectedFiles();
_deleteProgress = ValueNotifier("");
_scrollController = ScrollController();
deleteAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
);
if (!widget.debugScreen) {
_findSimilarImages();
@@ -121,6 +131,8 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
_isDisposed = true;
_selectedFiles.dispose();
_deleteProgress.dispose();
_scrollController.dispose();
deleteAnimationController.dispose();
super.dispose();
}
@@ -152,52 +164,59 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
ValueListenableBuilder(
valueListenable: _deleteProgress,
builder: (context, value, child) {
if (value.isEmpty) {
return const SizedBox.shrink();
}
final colorScheme = getEnteColorScheme(context);
final textTheme = getEnteTextTheme(context);
final fontFeatures = textTheme.small.fontFeatures ?? [];
return Container(
color: colorScheme.backgroundBase.withValues(alpha: 0.8),
child: Center(
return AnimatedCrossFade(
firstCurve: Curves.easeInOutExpo,
secondCurve: Curves.easeInOutExpo,
sizeCurve: Curves.easeInOutExpo,
crossFadeState: value.isEmpty
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 400),
secondChild: Align(
alignment: Alignment.center,
child: Container(
height: 42,
padding:
const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
const EdgeInsets.symmetric(horizontal: 16, vertical: 12)
.copyWith(left: 14),
decoration: BoxDecoration(
color: colorScheme.backgroundElevated,
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: colorScheme.strokeFaint,
blurRadius: 4,
offset: const Offset(0, 2),
),
],
color: Colors.black.withValues(alpha: 0.72),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor:
AlwaysStoppedAnimation(colorScheme.primary500),
child: SpinKitFadingCircle(
size: 18,
color: colorScheme.warning500,
controller: deleteAnimationController,
),
),
const SizedBox(width: 12),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context)
.deletingProgress(progress: value),
style: textTheme.body,
AppLocalizations.of(context).deletingDash,
style: textTheme.small.copyWith(color: Colors.white),
),
Text(
value,
style: textTheme.small.copyWith(
color: Colors.white,
fontFeatures: [
const FontFeature.tabularFigures(),
...fontFeatures,
],
),
),
],
),
),
),
firstChild: const SizedBox.shrink(),
);
},
),
@@ -326,28 +345,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
}
Widget _getLoadingView() {
final textTheme = getEnteTextTheme(context);
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 160,
child: RiveAnimation.asset(
'assets/ducky_analyze_files.riv',
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
Text(
AppLocalizations.of(context).analyzingPhotosLocally,
style: textTheme.bodyMuted,
textAlign: TextAlign.center,
),
],
),
);
return const _LoadingScreen();
}
Widget _getResultsView() {
@@ -405,12 +403,18 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
),
)
: ListView.builder(
controller: _scrollController,
cacheExtent: 400,
itemCount: _filteredGroups.length,
itemBuilder: (context, index) {
final similarFiles = _filteredGroups[index];
return RepaintBoundary(
child: _buildSimilarFilesGroup(similarFiles),
return Column(
children: [
if (index == 0) const SizedBox(height: 16),
RepaintBoundary(
child: _buildSimilarFilesGroup(similarFiles),
),
],
);
},
),
@@ -486,7 +490,9 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
final newSelection = <EnteFile>{};
for (final group in _filteredGroups) {
for (int i = 1; i < group.files.length; i++) {
newSelection.add(group.files[i]);
final file = group.files[i];
if (FavoritesService.instance.isFavoriteCache(file)) continue;
newSelection.add(file);
}
}
_selectedFiles.clearAll();
@@ -498,21 +504,24 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
return ListenableBuilder(
listenable: _selectedFiles,
builder: (context, _) {
final selectedFiles = _selectedFiles.files;
final selectedCount = selectedFiles.length;
final hasSelectedFiles = selectedCount > 0;
final eligibleFilteredFiles = <EnteFile>{};
int autoSelectCount = 0;
for (final group in _filteredGroups) {
for (int i = 1; i < group.files.length; i++) {
eligibleFilteredFiles.add(group.files[i]);
for (int i = 0; i < group.files.length; i++) {
final file = group.files[i];
eligibleFilteredFiles.add(file);
if (i != 0 && !FavoritesService.instance.isFavoriteCache(file)) {
autoSelectCount++;
}
}
}
final selectedFiles = _selectedFiles.files;
final selectedFilteredFiles =
selectedFiles.intersection(eligibleFilteredFiles);
final allFilteredSelected = eligibleFilteredFiles.isNotEmpty &&
selectedFilteredFiles.length == eligibleFilteredFiles.length;
selectedFilteredFiles.length >= autoSelectCount;
final hasSelectedFiles = selectedFilteredFiles.isNotEmpty;
int totalSize = 0;
for (final file in selectedFilteredFiles) {
@@ -571,6 +580,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
selectedFilteredFiles,
showDialog: true,
showUIFeedback: true,
scrollToTop: true,
);
},
),
@@ -590,7 +600,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
shouldSurfaceExecutionStates: false,
shouldShowSuccessConfirmation: false,
onTap: () async {
_toggleSelectAll();
_toggleSelectAll(allFilteredSelected);
},
),
),
@@ -602,22 +612,20 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
);
}
void _toggleSelectAll() {
final eligibleFiles = <EnteFile>{};
void _toggleSelectAll(bool allSelected) {
final autoSelectFiles = <EnteFile>{};
for (final group in _filteredGroups) {
for (int i = 1; i < group.files.length; i++) {
eligibleFiles.add(group.files[i]);
final file = group.files[i];
if (FavoritesService.instance.isFavoriteCache(file)) continue;
autoSelectFiles.add(file);
}
}
final currentSelected = _selectedFiles.files.intersection(eligibleFiles);
final allSelected = eligibleFiles.isNotEmpty &&
currentSelected.length == eligibleFiles.length;
if (allSelected) {
_selectedFiles.unSelectAll(eligibleFiles);
_selectedFiles.clearAll();
} else {
_selectedFiles.selectAll(eligibleFiles);
_selectedFiles.selectAll(autoSelectFiles);
}
}
@@ -646,7 +654,9 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
for (final group in _similarFilesList) {
if (group.files.length > 1) {
for (int i = 1; i < group.files.length; i++) {
_selectedFiles.toggleSelection(group.files[i]);
final file = group.files[i];
if (FavoritesService.instance.isFavoriteCache(file)) continue;
_selectedFiles.toggleSelection(file);
}
}
}
@@ -662,10 +672,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
await showGenericErrorDialog(context: context, error: e);
}
if (_isDisposed) return;
setState(() {
_pageState = SimilarImagesPageState.setup;
});
return;
Navigator.of(context).pop();
}
}
@@ -931,6 +938,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
Set<EnteFile> filesToDelete, {
bool showDialog = true,
bool showUIFeedback = true,
bool scrollToTop = false,
}) async {
if (filesToDelete.isEmpty) return;
if (showDialog) {
@@ -946,6 +954,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
filesToDelete,
true,
showUIFeedback: showUIFeedback,
scrollToTop: scrollToTop,
);
} catch (e, s) {
_logger.severe("Failed to delete files", e, s);
@@ -960,6 +969,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
filesToDelete,
true,
showUIFeedback: showUIFeedback,
scrollToTop: scrollToTop,
);
}
}
@@ -968,6 +978,7 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
Set<EnteFile> filesToDelete,
bool createSymlink, {
bool showUIFeedback = true,
bool scrollToTop = false,
}) async {
if (filesToDelete.isEmpty) {
return;
@@ -1050,6 +1061,15 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
setState(() {});
await deleteFilesFromRemoteOnly(context, allDeleteFiles.toList());
// Scroll to top if requested
if (scrollToTop && mounted) {
await _scrollController.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeOutCubic,
);
}
// Show congratulations popup
if (allDeleteFiles.length > 100 && mounted && showUIFeedback) {
final int totalSize = allDeleteFiles.fold<int>(
@@ -1183,3 +1203,83 @@ class _SimilarImagesPageState extends State<SimilarImagesPage> {
);
}
}
class _LoadingScreen extends StatefulWidget {
const _LoadingScreen();
@override
State<_LoadingScreen> createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<_LoadingScreen> {
Timer? _timer;
int _currentTextIndex = 0;
late List<String> _loadingTexts;
@override
void initState() {
super.initState();
_startTextCycling();
}
void _startTextCycling() {
_timer = Timer.periodic(const Duration(seconds: 7), (timer) {
if (_currentTextIndex < _loadingTexts.length - 1) {
if (mounted) {
setState(() {
_currentTextIndex++;
});
}
// Stop the timer when we reach the last text
if (_currentTextIndex >= _loadingTexts.length - 1) {
timer.cancel();
}
}
});
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final textTheme = getEnteTextTheme(context);
_loadingTexts = [
AppLocalizations.of(context).analyzingPhotosLocally,
AppLocalizations.of(context).lookingForVisualSimilarities,
AppLocalizations.of(context).comparingImageDetails,
AppLocalizations.of(context).findingSimilarImages,
AppLocalizations.of(context).almostDone,
];
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 160,
child: RiveAnimation.asset(
'assets/ducky_analyze_files.riv',
fit: BoxFit.contain,
),
),
const SizedBox(height: 16),
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: Text(
_loadingTexts[_currentTextIndex],
key: ValueKey<int>(_currentTextIndex),
style: textTheme.bodyMuted,
textAlign: TextAlign.center,
),
),
],
),
);
}
}

View File

@@ -80,7 +80,7 @@ class _SmartAlbumsStatusWidgetState extends State<SmartAlbumsStatusWidget>
.copyWith(left: 14),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Colors.black.withOpacity(0.65),
color: Colors.black.withValues(alpha: 0.65),
),
child: Row(
mainAxisSize: MainAxisSize.min,

View File

@@ -499,7 +499,8 @@ class FileAppBarState extends State<FileAppBar> {
widget.file.isUploaded &&
widget.file.fileSize != null &&
(widget.file.pubMagicMetadata?.sv ?? 0) != 1 &&
widget.file.ownerID == userId;
widget.file.ownerID == userId &&
VideoPreviewService.instance.isVideoStreamingEnabled;
}
Future<void> _handleVideoStream(String streamType) async {

View File

@@ -8,22 +8,24 @@ import 'package:photos/core/cache/thumbnail_in_memory_cache.dart';
import 'package:photos/core/constants.dart';
import 'package:photos/core/errors.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/core/exceptions.dart';
import 'package:photos/db/files_db.dart';
import 'package:photos/db/trash_db.dart';
import 'package:photos/events/files_updated_event.dart';
import "package:photos/events/local_photos_updated_event.dart";
import "package:photos/models/api/collection/user.dart";
import "package:photos/models/file/extensions/file_props.dart";
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/api/collection/user.dart';
import 'package:photos/models/file/extensions/file_props.dart';
import 'package:photos/models/file/file.dart';
import 'package:photos/models/file/file_type.dart';
import 'package:photos/models/file/trash_file.dart';
import 'package:photos/service_locator.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/favorites_service.dart';
import 'package:photos/ui/viewer/file/file_icons_widget.dart';
import "package:photos/ui/viewer/gallery/component/group/type.dart";
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
import 'package:photos/ui/viewer/gallery/component/group/type.dart';
import 'package:photos/ui/viewer/gallery/state/gallery_context_state.dart';
import 'package:photos/utils/file_util.dart';
import "package:photos/utils/standalone/task_queue.dart";
import 'package:photos/utils/standalone/task_queue.dart';
import 'package:photos/utils/thumbnail_util.dart';
class ThumbnailWidget extends StatefulWidget {
@@ -97,13 +99,20 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
if (!mounted && _localThumbnailQueueTaskId != null) {
if (widget.thumbnailSize == thumbnailLargeSize) {
largeLocalThumbnailQueue.removeTask(_localThumbnailQueueTaskId!);
_logger.info(
"Cancelled large thumbnail task: $_localThumbnailQueueTaskId",
);
} else if (widget.thumbnailSize == thumbnailSmallSize) {
smallLocalThumbnailQueue.removeTask(_localThumbnailQueueTaskId!);
_logger.info(
"Cancelled small thumbnail task: $_localThumbnailQueueTaskId",
);
}
}
// Cancel request only if the widget has been unmounted
if (!mounted && widget.file.isRemoteFile && !_hasLoadedThumbnail) {
removePendingGetThumbnailRequestIfAny(widget.file);
_logger.info("Cancelled thumbnail request for " + widget.file.tag);
}
});
}
@@ -116,17 +125,42 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
}
}
static final smallLocalThumbnailQueue = TaskQueue<String>(
maxConcurrentTasks: 15,
taskTimeout: const Duration(minutes: 1),
maxQueueSize: 200,
);
static final TaskQueue<String> smallLocalThumbnailQueue = _initSmallQueue();
static final TaskQueue<String> largeLocalThumbnailQueue = _initLargeQueue();
static final largeLocalThumbnailQueue = TaskQueue<String>(
maxConcurrentTasks: 5,
taskTimeout: const Duration(minutes: 1),
maxQueueSize: 200,
);
static TaskQueue<String> _initSmallQueue() {
final maxConcurrent = localSettings.smallQueueMaxConcurrent;
final timeoutSeconds = localSettings.smallQueueTimeoutSeconds;
final maxSize = localSettings.smallQueueMaxSize;
_logger.info(
"Initializing Small Local Thumbnail Queue - "
"MaxConcurrent: $maxConcurrent, Timeout: ${timeoutSeconds}s, MaxSize: $maxSize",
);
return TaskQueue<String>(
maxConcurrentTasks: maxConcurrent,
taskTimeout: Duration(seconds: timeoutSeconds),
maxQueueSize: maxSize,
);
}
static TaskQueue<String> _initLargeQueue() {
final maxConcurrent = localSettings.largeQueueMaxConcurrent;
final timeoutSeconds = localSettings.largeQueueTimeoutSeconds;
final maxSize = localSettings.largeQueueMaxSize;
_logger.info(
"Initializing Large Local Thumbnail Queue - "
"MaxConcurrent: $maxConcurrent, Timeout: ${timeoutSeconds}s, MaxSize: $maxSize",
);
return TaskQueue<String>(
maxConcurrentTasks: maxConcurrent,
taskTimeout: Duration(seconds: timeoutSeconds),
maxQueueSize: maxSize,
);
}
///Assigned dimension will be the size of a grid item. The size will be
///assigned to the side which is smaller in dimension.
@@ -229,6 +263,32 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
if (widget.shouldShowPinIcon) {
viewChildren.add(const PinOverlayIcon());
}
if (localSettings.showLocalIDOverThumbnails &&
widget.file.localID != null) {
viewChildren.add(
Positioned(
bottom: 4,
left: 4,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: const Color.fromRGBO(0, 0, 0, 0.8),
borderRadius: BorderRadius.circular(4),
),
child: FittedBox(
child: Text(
"${widget.file.localID}",
style: const TextStyle(
color: Colors.white,
fontSize: 8,
fontWeight: FontWeight.w600,
),
),
),
),
),
);
}
return Stack(
clipBehavior: Clip.none,
@@ -307,8 +367,18 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
thumbnailSmallSize,
);
}).catchError((e) {
_logger.warning("Could not load thumbnail from disk: ", e);
_errorLoadingLocalThumbnail = true;
if (e is WidgetUnmountedException) {
// Widget was unmounted - this is expected behavior
_logger.fine(
"Thumbnail loading cancelled: widget unmounted for localID: ${widget.file.localID}",
);
} else {
_logger.warning(
"Could not load thumbnail from disk for localID: ${widget.file.localID}",
e,
);
}
});
}
@@ -326,7 +396,9 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
}
//Do not retry if the widget is not mounted
if (!mounted) {
return null;
throw WidgetUnmountedException(
"Thumbnail loading cancelled: widget unmounted",
);
}
retryAttempts++;
@@ -335,7 +407,7 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
);
if (retryAttempts <= _maxLocalThumbnailRetries) {
_logger.warning(
"Error getting local thumbnail for ${widget.file.displayName}, retrying (attempt $retryAttempts) in ${backoff.inMilliseconds} ms",
"Error getting local thumbnail for ${widget.file.displayName} (localID: ${widget.file.localID}) due to ${e.runtimeType}, retrying (attempt $retryAttempts) in ${backoff.inMilliseconds} ms",
e,
);
await Future.delayed(backoff); // Exponential backoff
@@ -366,11 +438,16 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
}
await relevantTaskQueue.addTask(_localThumbnailQueueTaskId!, () async {
final thumbnailBytes = await getThumbnailFromLocal(
widget.file,
size: widget.thumbnailSize,
);
completer.complete(thumbnailBytes);
late final Uint8List? thumbnailBytes;
try {
thumbnailBytes = await getThumbnailFromLocal(
widget.file,
size: widget.thumbnailSize,
);
completer.complete(thumbnailBytes);
} catch (e) {
completer.completeError(e);
}
});
return completer.future;

View File

@@ -32,27 +32,34 @@ class VideoStreamChangeWidget extends StatefulWidget {
class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
StreamSubscription<VideoPreviewStateChangedEvent>? _subscription;
bool isCurrentlyProcessing = false;
@override
void initState() {
super.initState();
// Initialize processing state safely in initState
isCurrentlyProcessing = VideoPreviewService.instance
.isCurrentlyProcessing(widget.file.uploadedFileID);
_subscription =
Bus.instance.on<VideoPreviewStateChangedEvent>().listen((event) {
final fileId = event.fileId;
if (widget.file.uploadedFileID != fileId) {
return; // Not for this file
}
final status = event.status;
// Handle different states
switch (status) {
case PreviewItemStatus.inQueue:
case PreviewItemStatus.uploaded:
case PreviewItemStatus.failed:
setState(() {});
break;
default:
// Handle different states - will be false for different files or non-processing states
final newProcessingState = widget.file.uploadedFileID == fileId && switch (status) {
PreviewItemStatus.inQueue ||
PreviewItemStatus.retry ||
PreviewItemStatus.compressing ||
PreviewItemStatus.uploading =>
true,
_ => false,
};
// Only update state if value changed
if (isCurrentlyProcessing != newProcessingState) {
isCurrentlyProcessing = newProcessingState;
setState(() {});
}
});
}
@@ -63,14 +70,28 @@ class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
super.dispose();
}
String _getStatusText(BuildContext context, PreviewItemStatus? status) {
switch (status) {
case PreviewItemStatus.inQueue:
case PreviewItemStatus.retry:
return AppLocalizations.of(context).queued;
case PreviewItemStatus.compressing:
case PreviewItemStatus.uploading:
default:
return AppLocalizations.of(context).creatingStream;
}
}
@override
Widget build(BuildContext context) {
final bool isPreviewAvailable = widget.file.uploadedFileID != null &&
(fileDataService.previewIds.containsKey(widget.file.uploadedFileID));
// Check if this file is currently being processed for streaming
final bool isCurrentlyProcessing = VideoPreviewService.instance
.isCurrentlyProcessing(widget.file.uploadedFileID);
// Get the current processing status for more specific messaging
final processingStatus = widget.file.uploadedFileID != null
? VideoPreviewService.instance
.getProcessingStatus(widget.file.uploadedFileID!)
: null;
final colorScheme = getEnteColorScheme(context);
@@ -125,7 +146,7 @@ class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
),
const SizedBox(width: 8),
Text(
AppLocalizations.of(context).creatingStream,
_getStatusText(context, processingStatus),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@@ -604,7 +604,6 @@ class GalleryState extends State<Gallery> {
? const NeverScrollableScrollPhysics()
: const ExponentialBouncingScrollPhysics(),
controller: _scrollController,
cacheExtent: galleryCacheExtent,
slivers: [
SliverToBoxAdapter(
child: SizeChangedLayoutNotifier(
@@ -642,25 +641,6 @@ class GalleryState extends State<Gallery> {
),
);
}
double get galleryCacheExtent {
final int photoGridSize = localSettings.getPhotoGridSize();
switch (photoGridSize) {
case 2:
case 3:
return 1000;
case 4:
return 850;
case 5:
return 600;
case 6:
return 300;
default:
throw StateError(
'Invalid photo grid size configuration: $photoGridSize',
);
}
}
}
class PinnedGroupHeader extends StatefulWidget {

View File

@@ -279,7 +279,7 @@ class ScrollBarDivider extends StatelessWidget {
// is affected.
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
color: Colors.black.withValues(alpha: 0.1),
blurRadius: 3,
offset: const Offset(0, 2),
),

View File

@@ -53,11 +53,11 @@ const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
/// {@end-tool}
///
/// A scrollbar track can be added using [trackVisibility]. This can also be
/// drawn when triggered by a hover event, or based on any [MaterialState] by
/// drawn when triggered by a hover event, or based on any [WidgetState] by
/// using [ScrollbarThemeData.trackVisibility].
///
/// The [thickness] of the track and scrollbar thumb can be changed dynamically
/// in response to [MaterialState]s using [ScrollbarThemeData.thickness].
/// in response to [WidgetState]s using [ScrollbarThemeData.thickness].
///
/// See also:
///
@@ -262,17 +262,17 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
late Color idleColor;
switch (brightness) {
case Brightness.light:
dragColor = onSurface.withOpacity(0.6);
hoverColor = onSurface.withOpacity(0.5);
dragColor = onSurface.withValues(alpha: 0.6);
hoverColor = onSurface.withValues(alpha: 0.5);
idleColor = _useAndroidScrollbar
? Theme.of(context).highlightColor.withOpacity(1.0)
: onSurface.withOpacity(0.1);
? Theme.of(context).highlightColor.withValues(alpha: 1.0)
: onSurface.withValues(alpha: 0.1);
case Brightness.dark:
dragColor = onSurface.withOpacity(0.75);
hoverColor = onSurface.withOpacity(0.65);
dragColor = onSurface.withValues(alpha: 0.75);
hoverColor = onSurface.withValues(alpha: 0.65);
idleColor = _useAndroidScrollbar
? Theme.of(context).highlightColor.withOpacity(1.0)
: onSurface.withOpacity(0.3);
? Theme.of(context).highlightColor.withValues(alpha: 1.0)
: onSurface.withValues(alpha: 0.3);
}
return WidgetStateProperty.resolveWith((Set<WidgetState> states) {
@@ -304,8 +304,8 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
if (showScrollbar && _trackVisibility.resolve(states)) {
return _scrollbarTheme.trackColor?.resolve(states) ??
switch (brightness) {
Brightness.light => onSurface.withOpacity(0.03),
Brightness.dark => onSurface.withOpacity(0.05),
Brightness.light => onSurface.withValues(alpha: 0.03),
Brightness.dark => onSurface.withValues(alpha: 0.05),
};
}
return const Color(0x00000000);
@@ -322,8 +322,8 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
if (showScrollbar && _trackVisibility.resolve(states)) {
return _scrollbarTheme.trackBorderColor?.resolve(states) ??
switch (brightness) {
Brightness.light => onSurface.withOpacity(0.1),
Brightness.dark => onSurface.withOpacity(0.25),
Brightness.light => onSurface.withValues(alpha: 0.1),
Brightness.dark => onSurface.withValues(alpha: 0.25),
};
}
return const Color(0x00000000);

View File

@@ -41,6 +41,15 @@ class LocalSettings {
"hide_shared_items_from_home_gallery";
static const kCollectionViewType = "collection_view_type";
static const kCollectionSortDirection = "collection_sort_direction";
static const kShowLocalIDOverThumbnails = "show_local_id_over_thumbnails";
// Thumbnail queue configuration keys
static const kSmallQueueMaxConcurrent = "small_queue_max_concurrent";
static const kSmallQueueTimeout = "small_queue_timeout_seconds";
static const kSmallQueueMaxSize = "small_queue_max_size";
static const kLargeQueueMaxConcurrent = "large_queue_max_concurrent";
static const kLargeQueueTimeout = "large_queue_timeout_seconds";
static const kLargeQueueMaxSize = "large_queue_max_size";
final SharedPreferences _prefs;
@@ -217,4 +226,59 @@ class LocalSettings {
bool get hideSharedItemsFromHomeGallery =>
_prefs.getBool(_hideSharedItemsFromHomeGalleryTag) ?? false;
bool get showLocalIDOverThumbnails =>
_prefs.getBool(kShowLocalIDOverThumbnails) ?? false;
Future<void> setShowLocalIDOverThumbnails(bool value) async {
await _prefs.setBool(kShowLocalIDOverThumbnails, value);
}
// Thumbnail queue configuration - Small queue
int get smallQueueMaxConcurrent => _prefs.getInt(kSmallQueueMaxConcurrent) ?? 15;
int get smallQueueTimeoutSeconds => _prefs.getInt(kSmallQueueTimeout) ?? 60;
int get smallQueueMaxSize => _prefs.getInt(kSmallQueueMaxSize) ?? 200;
Future<void> setSmallQueueMaxConcurrent(int value) async {
await _prefs.setInt(kSmallQueueMaxConcurrent, value);
}
Future<void> setSmallQueueTimeout(int seconds) async {
await _prefs.setInt(kSmallQueueTimeout, seconds);
}
Future<void> setSmallQueueMaxSize(int value) async {
await _prefs.setInt(kSmallQueueMaxSize, value);
}
// Thumbnail queue configuration - Large queue
int get largeQueueMaxConcurrent => _prefs.getInt(kLargeQueueMaxConcurrent) ?? 5;
int get largeQueueTimeoutSeconds => _prefs.getInt(kLargeQueueTimeout) ?? 60;
int get largeQueueMaxSize => _prefs.getInt(kLargeQueueMaxSize) ?? 200;
Future<void> setLargeQueueMaxConcurrent(int value) async {
await _prefs.setInt(kLargeQueueMaxConcurrent, value);
}
Future<void> setLargeQueueTimeout(int seconds) async {
await _prefs.setInt(kLargeQueueTimeout, seconds);
}
Future<void> setLargeQueueMaxSize(int value) async {
await _prefs.setInt(kLargeQueueMaxSize, value);
}
// Reset thumbnail queue settings to defaults
Future<void> resetThumbnailQueueSettings() async {
await _prefs.remove(kSmallQueueMaxConcurrent);
await _prefs.remove(kSmallQueueTimeout);
await _prefs.remove(kSmallQueueMaxSize);
await _prefs.remove(kLargeQueueMaxConcurrent);
await _prefs.remove(kLargeQueueTimeout);
await _prefs.remove(kLargeQueueMaxSize);
}
}

View File

@@ -9,22 +9,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "85.0.0"
_flutterfire_internals:
dependency: transitive
description:
name: _flutterfire_internals
sha256: a5788040810bd84400bc209913fbc40f388cded7cdf95ee2f5d2bff7e38d5241
url: "https://pub.dev"
source: hosted
version: "1.3.58"
adaptive_theme:
dependency: "direct main"
description:
name: adaptive_theme
sha256: caa49b4c73b681bf12a641dff77aa1383262a00cf38b9d1a25b180e275ba5ab9
sha256: "41b8af1bb5f3fb87db1aed8c8a2dc3eab79116d20793b5c9b6d2e8809402de9a"
url: "https://pub.dev"
source: hosted
version: "3.7.0"
version: "3.7.1+2"
analyzer:
dependency: transitive
description:
@@ -37,10 +29,10 @@ packages:
dependency: "direct main"
description:
name: android_intent_plus
sha256: dfc1fd3a577205ae8f11e990fb4ece8c90cceabbee56fcf48e463ecf0bd6aae3
sha256: "2329378af63f49b985cb2e110ac784d08374f1e2b1984be77ba9325b1c8cce11"
url: "https://pub.dev"
source: hosted
version: "5.3.0"
version: "5.3.1"
animated_list_plus:
dependency: "direct main"
description:
@@ -69,10 +61,10 @@ packages:
dependency: "direct main"
description:
name: app_links
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8"
url: "https://pub.dev"
source: hosted
version: "6.4.0"
version: "6.4.1"
app_links_linux:
dependency: transitive
description:
@@ -166,10 +158,10 @@ packages:
dependency: transitive
description:
name: build
sha256: "7d95cbbb1526ab5ae977df9b4cc660963b9b27f6d1075c0b34653868911385e4"
sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.1.0"
build_cli_annotations:
dependency: transitive
description:
@@ -182,10 +174,10 @@ packages:
dependency: transitive
description:
name: build_config
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.2.0"
build_daemon:
dependency: transitive
description:
@@ -198,26 +190,26 @@ packages:
dependency: transitive
description:
name: build_resolvers
sha256: "38c9c339333a09b090a638849a4c56e70a404c6bdd3b511493addfbc113b60c2"
sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.3"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: b971d4a1c789eba7be3e6fe6ce5e5b50fd3719e3cb485b3fad6d04358304351d
sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.7.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: c04e612ca801cd0928ccdb891c263a2b1391cb27940a5ea5afcf9ba894de5d62
sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b"
url: "https://pub.dev"
source: hosted
version: "9.2.0"
version: "9.3.1"
built_collection:
dependency: transitive
description:
@@ -230,10 +222,10 @@ packages:
dependency: transitive
description:
name: built_value
sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d
url: "https://pub.dev"
source: hosted
version: "8.10.1"
version: "8.12.0"
cached_network_image:
dependency: "direct main"
description:
@@ -279,10 +271,10 @@ packages:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
version: "2.0.4"
chewie:
dependency: "direct main"
description:
@@ -345,10 +337,10 @@ packages:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "051849e2bd7c7b3bc5844ea0d096609ddc3a859890ec3a9ac4a65a2620cc1f99"
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
url: "https://pub.dev"
source: hosted
version: "6.1.4"
version: "6.1.5"
connectivity_plus_platform_interface:
dependency: transitive
description:
@@ -369,18 +361,18 @@ packages:
dependency: transitive
description:
name: coverage
sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080
sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d"
url: "https://pub.dev"
source: hosted
version: "1.14.1"
version: "1.15.0"
cronet_http:
dependency: transitive
description:
name: cronet_http
sha256: "5ed075c59b2d4bd43af4e73d906b8082e98ecd2af9c625327370ef28361bf635"
sha256: "1b99ad5ae81aa9d2f12900e5f17d3681f3828629bb7f7fe7ad88076a34209840"
url: "https://pub.dev"
source: hosted
version: "1.3.4"
version: "1.5.0"
cross_file:
dependency: transitive
description:
@@ -409,10 +401,10 @@ packages:
dependency: transitive
description:
name: cupertino_http
sha256: "8fb9e2c36d0732d9d96abd76683406b57e78a2514e27c962e0c603dbe6f2e3f8"
sha256: "72187f715837290a63479a5b0ae709f4fedad0ed6bd0441c275eceaa02d5abae"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.3.0"
cupertino_icons:
dependency: "direct main"
description:
@@ -433,10 +425,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.1.1"
dart_ui_isolate:
dependency: "direct main"
description:
@@ -481,10 +473,10 @@ packages:
dependency: "direct main"
description:
name: dio
sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.8.0+1"
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
@@ -671,7 +663,7 @@ packages:
description:
path: "flutter/flutter"
ref: remove-event-sub
resolved-ref: "2c0f34797df830ef61e6ed479583b368c342cb37"
resolved-ref: b7aac7903f70dce6d71506e221066af1a53ec7fc
url: "https://github.com/ente-io/ffmpeg-kit"
source: git
version: "6.0.3"
@@ -680,7 +672,7 @@ packages:
description:
path: "flutter/flutter_platform_interface"
ref: remove-event-sub
resolved-ref: "2c0f34797df830ef61e6ed479583b368c342cb37"
resolved-ref: b7aac7903f70dce6d71506e221066af1a53ec7fc
url: "https://github.com/ente-io/ffmpeg-kit"
source: git
version: "0.2.1"
@@ -708,54 +700,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.2.14"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: c6e8a6bf883d8ddd0dec39be90872daca65beaa6f4cff0051ed3b16c56b82e9f
url: "https://pub.dev"
source: hosted
version: "3.15.1"
firebase_core_platform_interface:
file_selector_linux:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb"
name: file_selector_linux
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
firebase_core_web:
version: "0.9.3+2"
file_selector_macos:
dependency: transitive
description:
name: firebase_core_web
sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37"
name: file_selector_macos
sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c"
url: "https://pub.dev"
source: hosted
version: "2.24.1"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: "0f3363f97672eb9f65609fa00ed2f62cc8ec93e7e2d4def99726f9165d3d8a73"
url: "https://pub.dev"
source: hosted
version: "15.2.9"
firebase_messaging_platform_interface:
version: "0.9.4+4"
file_selector_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: "7a05ef119a14c5f6a9440d1e0223bcba20c8daf555450e119c4c477bf2c3baa9"
name: file_selector_platform_interface
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.dev"
source: hosted
version: "4.6.9"
firebase_messaging_web:
version: "2.6.2"
file_selector_windows:
dependency: transitive
description:
name: firebase_messaging_web
sha256: a4547f76da2a905190f899eb4d0150e1d0fd52206fce469d9f05ae15bb68b2c5
name: file_selector_windows
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
url: "https://pub.dev"
source: hosted
version: "3.10.9"
version: "0.9.3+4"
fixnum:
dependency: "direct main"
description:
@@ -954,10 +930,10 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: edae0c34573233ab03f5ba1f07465e55c384743893042cb19e010b4ee8541c12
sha256: a9966c850de5e445331b854fa42df96a8020066d67f125a5964cbc6556643f68
url: "https://pub.dev"
source: hosted
version: "19.3.0"
version: "19.4.1"
flutter_local_notifications_linux:
dependency: transitive
description:
@@ -978,10 +954,10 @@ packages:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: f8fc0652a601f83419d623c85723a3e82ad81f92b33eaa9bcc21ea1b94773e6e
sha256: ed46d7ae4ec9d19e4c8fa2badac5fe27ba87a3fe387343ce726f927af074ec98
url: "https://pub.dev"
source: hosted
version: "1.0.0"
version: "1.0.2"
flutter_localizations:
dependency: "direct main"
description: flutter
@@ -1031,10 +1007,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e
sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31
url: "https://pub.dev"
source: hosted
version: "2.0.28"
version: "2.0.30"
flutter_rust_bridge:
dependency: "direct main"
description:
@@ -1112,10 +1088,10 @@ packages:
dependency: "direct main"
description:
name: flutter_spinkit
sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472
sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f"
url: "https://pub.dev"
source: hosted
version: "5.2.1"
version: "5.2.2"
flutter_staggered_grid_view:
dependency: "direct main"
description:
@@ -1128,10 +1104,10 @@ packages:
dependency: "direct main"
description:
name: flutter_svg
sha256: cd57f7969b4679317c17af6fd16ee233c1e60a82ed209d8a475c54fd6fd6f845
sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678
url: "https://pub.dev"
source: hosted
version: "2.2.0"
version: "2.2.1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -1255,10 +1231,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.0"
http_client_helper:
dependency: transitive
description:
@@ -1299,38 +1275,102 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.4"
in_app_purchase:
image_editor:
dependency: "direct main"
description:
name: in_app_purchase
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
name: image_editor
sha256: "38070067264fd9fea4328ca630d2ff7bd65ebe6aa4ed375d983b732d2ae7146b"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
in_app_purchase_android:
version: "1.6.0"
image_editor_common:
dependency: transitive
description:
name: in_app_purchase_android
sha256: fd76e5612da6facadcfe8a3477da092908227260a9f6ec7db9a66dd989c69b02
name: image_editor_common
sha256: "93d2f5c8b636f862775dd62a9ec20d09c8272598daa02f935955a4640e1844ee"
url: "https://pub.dev"
source: hosted
version: "0.4.0+2"
in_app_purchase_platform_interface:
version: "1.2.0"
image_editor_ohos:
dependency: transitive
description:
name: in_app_purchase_platform_interface
sha256: "1d353d38251da5b9fea6635c0ebfc6bb17a2d28d0e86ea5e083bf64244f1fb4c"
name: image_editor_ohos
sha256: "06756859586d5acefec6e3b4f356f9b1ce05ef09213bcb9a0ce1680ecea2d054"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
in_app_purchase_storekit:
version: "0.0.9"
image_editor_platform_interface:
dependency: transitive
description:
name: in_app_purchase_storekit
sha256: ceddd5a70d268f762d29993ed470054bc2baf8793e41800bc82cde05110260d0
name: image_editor_platform_interface
sha256: "474517efc770464f7d99942472d8cfb369a3c378e95466ec17f74d2b80bd40de"
url: "https://pub.dev"
source: hosted
version: "0.4.2"
version: "1.1.0"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e"
url: "https://pub.dev"
source: hosted
version: "0.8.13+1"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e
url: "https://pub.dev"
source: hosted
version: "0.8.13"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04
url: "https://pub.dev"
source: hosted
version: "0.2.2"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae
url: "https://pub.dev"
source: hosted
version: "0.2.2"
integration_test:
dependency: "direct dev"
description: flutter
@@ -1348,10 +1388,10 @@ packages:
dependency: "direct dev"
description:
name: intl_utils
sha256: "3b2655259dbebd26e3b1466d0839f389dc59a275337e1b760ac645bc7814d2d3"
sha256: da67ac187b521445d745f7c68e7254c2090f6c3c9081c93cd515480af9e59569
url: "https://pub.dev"
source: hosted
version: "2.8.10"
version: "2.8.11"
io:
dependency: transitive
description:
@@ -1372,10 +1412,10 @@ packages:
dependency: transitive
description:
name: jni
sha256: "459727a9daf91bdfb39b014cf3c186cf77f0136124a274ac83c186e12262ac4e"
sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1
url: "https://pub.dev"
source: hosted
version: "0.12.2"
version: "0.14.2"
js:
dependency: "direct overridden"
description:
@@ -1396,10 +1436,10 @@ packages:
dependency: "direct dev"
description:
name: json_serializable
sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27
sha256: "33a040668b31b320aafa4822b7b1e177e163fc3c1e835c6750319d4ab23aa6fe"
url: "https://pub.dev"
source: hosted
version: "6.10.0"
version: "6.11.1"
latlong2:
dependency: "direct main"
description:
@@ -1476,18 +1516,18 @@ packages:
dependency: "direct main"
description:
name: local_auth_android
sha256: "63ad7ca6396290626dc0cb34725a939e4cfe965d80d36112f08d49cf13a8136e"
sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3"
url: "https://pub.dev"
source: hosted
version: "1.0.49"
version: "1.0.52"
local_auth_darwin:
dependency: transitive
description:
name: local_auth_darwin
sha256: "25163ce60a5a6c468cf7a0e3dc8a165f824cabc2aa9e39a5e9fc5c2311b7686f"
sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
version: "1.6.0"
local_auth_ios:
dependency: "direct main"
description:
@@ -1516,10 +1556,10 @@ packages:
dependency: transitive
description:
name: logger
sha256: "2621da01aabaf223f8f961e751f2c943dbb374dc3559b982f200ccedadaa6999"
sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c"
url: "https://pub.dev"
source: hosted
version: "2.6.0"
version: "2.6.1"
logging:
dependency: "direct main"
description:
@@ -1749,10 +1789,10 @@ packages:
dependency: "direct main"
description:
name: native_dio_adapter
sha256: "7420bc9517b2abe09810199a19924617b45690a44ecfb0616ac9babc11875c03"
sha256: "1c51bd42027861d27ccad462ba0903f5e3197461cc6d59a0bb8658cb5ad7bd01"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.5.0"
native_video_player:
dependency: "direct main"
description:
@@ -1838,18 +1878,18 @@ packages:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev"
source: hosted
version: "8.3.0"
version: "8.3.1"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
version: "3.2.1"
panorama:
dependency: "direct main"
description:
@@ -1903,18 +1943,18 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9
sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db"
url: "https://pub.dev"
source: hosted
version: "2.2.17"
version: "2.2.18"
path_provider_foundation:
dependency: "direct main"
description:
name: path_provider_foundation
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.2"
path_provider_linux:
dependency: transitive
description:
@@ -1991,10 +2031,10 @@ packages:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
version: "7.0.1"
photo_manager:
dependency: "direct main"
description:
@@ -2023,10 +2063,10 @@ packages:
dependency: "direct main"
description:
name: pinput
sha256: "8a73be426a91fefec90a7f130763ca39772d547e92f19a827cf4aa02e323d35a"
sha256: c41f42ee301505ae2375ec32871c985d3717bf8aee845620465b286e0140aad2
url: "https://pub.dev"
source: hosted
version: "5.0.1"
version: "5.0.2"
platform:
dependency: transitive
description:
@@ -2120,10 +2160,10 @@ packages:
dependency: transitive
description:
name: provider
sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84"
sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
url: "https://pub.dev"
source: hosted
version: "6.1.5"
version: "6.1.5+1"
pub_semver:
dependency: transitive
description:
@@ -2208,10 +2248,10 @@ packages:
dependency: transitive
description:
name: screen_brightness_android
sha256: fb5fa43cb89d0c9b8534556c427db1e97e46594ac5d66ebdcf16063b773d54ed
sha256: d34f5321abd03bc3474f4c381f53d189117eba0b039eac1916aa92cca5fd0a96
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.3"
screen_brightness_platform_interface:
dependency: transitive
description:
@@ -2256,18 +2296,18 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0
sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1
url: "https://pub.dev"
source: hosted
version: "11.0.0"
version: "11.1.0"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef"
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
version: "6.1.0"
shared_preferences:
dependency: "direct main"
description:
@@ -2280,10 +2320,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74
url: "https://pub.dev"
source: hosted
version: "2.4.10"
version: "2.4.12"
shared_preferences_foundation:
dependency: transitive
description:
@@ -2373,10 +2413,10 @@ packages:
dependency: transitive
description:
name: source_helper
sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca
sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723"
url: "https://pub.dev"
source: hosted
version: "1.3.7"
version: "1.3.8"
source_map_stack_trace:
dependency: transitive
description:
@@ -2429,10 +2469,10 @@ packages:
dependency: transitive
description:
name: sqflite_common
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
url: "https://pub.dev"
source: hosted
version: "2.5.5"
version: "2.5.6"
sqflite_darwin:
dependency: transitive
description:
@@ -2461,34 +2501,34 @@ packages:
dependency: transitive
description:
name: sqlite3
sha256: c0503c69b44d5714e6abbf4c1f51a3c3cc42b75ce785f44404765e4635481d38
sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
url: "https://pub.dev"
source: hosted
version: "2.7.6"
version: "2.9.0"
sqlite3_flutter_libs:
dependency: "direct main"
description:
name: sqlite3_flutter_libs
sha256: e07232b998755fe795655c56d1f5426e0190c9c435e1752d39e7b1cd33699c71
sha256: "2b03273e71867a8a4d030861fc21706200debe5c5858a4b9e58f4a1c129586a4"
url: "https://pub.dev"
source: hosted
version: "0.5.34"
version: "0.5.39"
sqlite3_web:
dependency: transitive
description:
name: sqlite3_web
sha256: "967e076442f7e1233bd7241ca61f3efe4c7fc168dac0f38411bdb3bdf471eb3c"
sha256: "0f6ebcb4992d1892ac5c8b5ecd22a458ab9c5eb6428b11ae5ecb5d63545844da"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
version: "0.3.2"
sqlite_async:
dependency: "direct main"
description:
name: sqlite_async
sha256: "9332aedd311a19dd215dcb55729bc68dc587dc7655b569ab8819b68ee0be0082"
sha256: "4da3b035bcce83d10beae879c6539e75230826bba95ed440565b6afbdd8b1550"
url: "https://pub.dev"
source: hosted
version: "0.11.7"
version: "0.11.8"
stack_trace:
dependency: transitive
description:
@@ -2565,10 +2605,10 @@ packages:
dependency: "direct main"
description:
name: synchronized
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
version: "3.3.1"
version: "3.4.0"
system_info_plus:
dependency: "direct main"
description:
@@ -2701,26 +2741,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.1"
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7"
url: "https://pub.dev"
source: hosted
version: "6.3.16"
version: "6.3.18"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7
url: "https://pub.dev"
source: hosted
version: "6.3.3"
version: "6.3.4"
url_launcher_linux:
dependency: transitive
description:
@@ -2733,10 +2773,10 @@ packages:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f
url: "https://pub.dev"
source: hosted
version: "3.2.2"
version: "3.2.3"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -2789,10 +2829,10 @@ packages:
dependency: transitive
description:
name: vector_graphics_compiler
sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331"
sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc
url: "https://pub.dev"
source: hosted
version: "1.1.17"
version: "1.1.19"
vector_math:
dependency: transitive
description:
@@ -2839,18 +2879,18 @@ packages:
dependency: transitive
description:
name: video_player_android
sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73"
sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385"
url: "https://pub.dev"
source: hosted
version: "2.8.7"
version: "2.8.13"
video_player_avfoundation:
dependency: transitive
description:
name: video_player_avfoundation
sha256: "0d47db6cbf72db61d86369219efd35c7f9d93515e1319da941ece81b1f21c49c"
sha256: f9a780aac57802b2892f93787e5ea53b5f43cc57dc107bee9436458365be71cd
url: "https://pub.dev"
source: hosted
version: "2.7.2"
version: "2.8.4"
video_player_platform_interface:
dependency: transitive
description:
@@ -2863,10 +2903,10 @@ packages:
dependency: transitive
description:
name: video_player_web
sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba
sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.4.0"
video_thumbnail:
dependency: "direct main"
description:
@@ -2919,10 +2959,10 @@ packages:
dependency: "direct overridden"
description:
name: watcher
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c"
url: "https://pub.dev"
source: hosted
version: "1.1.2"
version: "1.1.3"
web:
dependency: transitive
description:
@@ -2967,18 +3007,18 @@ packages:
dependency: "direct main"
description:
name: wechat_assets_picker
sha256: cafe3d32564ed3cacf9822f251941f7b44fe9885c17c8de4fca7e939a459e1ef
sha256: c307e50394c1e6dfcd5c4701e84efb549fce71444fedcf2e671c50d809b3e2a1
url: "https://pub.dev"
source: hosted
version: "9.5.1"
version: "9.8.0"
wechat_picker_library:
dependency: transitive
description:
name: wechat_picker_library
sha256: a42e09cb85b15fc9410f6a69671371cc60aa99c4a1f7967f6593a7f665f6f47a
sha256: "5cb61b9aa935b60da5b043f8446fbb9c5077419f20ccc4856bf444aec4f44bc1"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.7"
widgets_to_image:
dependency: "direct main"
description:
@@ -3031,10 +3071,10 @@ packages:
dependency: "direct main"
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
url: "https://pub.dev"
source: hosted
version: "6.5.0"
version: "6.6.1"
xmlstream:
dependency: transitive
description:

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