Merge branch 'main' into test-com
This commit is contained in:
9
.github/workflows/mobile-daily-internal.yml
vendored
9
.github/workflows/mobile-daily-internal.yml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -38,6 +39,14 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Increment version code for build
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
name: "Internal release (photos with rust)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile/apps/photos
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_photos_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
run: |
|
||||
flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.photos
|
||||
releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
- name: Notify Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
|
||||
nodetail: true
|
||||
title: "🏆 Internal release available for Photos"
|
||||
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos)"
|
||||
color: 0x00ff00
|
||||
@@ -5,6 +5,7 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -35,6 +36,14 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
|
||||
@@ -26,6 +26,10 @@ export const sidebar = [
|
||||
text: "Collecting photos",
|
||||
link: "/photos/features/collect",
|
||||
},
|
||||
{
|
||||
text: "Custom domains",
|
||||
link: "/photos/features/custom-domains/",
|
||||
},
|
||||
{
|
||||
text: "Deduplicate",
|
||||
link: "/photos/features/deduplicate",
|
||||
|
||||
@@ -35,4 +35,4 @@ be specific to your distro (e.g. `xdg-desktop-menu forceupdate`).
|
||||
> [!NOTE]
|
||||
>
|
||||
> If you're using an AppImage and not seeing the icon, you'll need to
|
||||
> [enable AppImage desktop integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration).
|
||||
> [enable AppImage desktop integration](/photos/troubleshooting/desktop-install/#appimage-desktop-integration).
|
||||
|
||||
BIN
docs/docs/photos/features/custom-domains/cf.png
Normal file
BIN
docs/docs/photos/features/custom-domains/cf.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
105
docs/docs/photos/features/custom-domains/index.md
Normal file
105
docs/docs/photos/features/custom-domains/index.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
title: Custom domains
|
||||
description: Use your own domain when sharing photos and videos stored in Ente Photos
|
||||
---
|
||||
|
||||
# Custom domains
|
||||
|
||||
Custom domains allow you to serve your public links with your own personalized domain.
|
||||
|
||||
For example, if I have an Ente album and wish to share it with my friends, I can go to the album's sharing settings and create a public link. When I copy this link, it will of the form of
|
||||
|
||||
```
|
||||
https://albums.ente.io/?t=...
|
||||
```
|
||||
|
||||
The custom domains feature allows you to instead create a link that uses your own domain, say
|
||||
|
||||
```
|
||||
https://pics.example.org/?t=...
|
||||
```
|
||||
|
||||
You don't need to run any servers or manage any services, Ente will still host and serve your album for you, the only thing that changes is that you can serve your links using your personalized domain.
|
||||
|
||||
## Availability
|
||||
|
||||
The custom domains feature requires the ability to publicly share albums which for abuse prevention reasons can only be done by people with an active Ente subscription.
|
||||
|
||||
## Setup
|
||||
|
||||
The setup involves two steps:
|
||||
|
||||
1. Letting Ente know about the domain you wish to use for serving your public links
|
||||
2. Updating your DNS settings to point your domain (or subdomain) to **my.ente.io**
|
||||
|
||||
For people who are comfortable with changing DNS settings on their domain provider, this entire process is very simple will take a minute. For people who are not comfortable with changing DNS, we will provide a more detailed breakdown below.
|
||||
|
||||
Let's dive in.
|
||||
|
||||
To make the process concrete, let's assume we're trying to use _pics.example.org_ as our custom domain. Note that there is no restriction to use a subdomain, a top level domain can be used as a custom domain too. That is, either of _example.org_ or _subdomain.example.org_ is fine, Ente will work with both.
|
||||
|
||||
### Step 1 - Link your domain
|
||||
|
||||
The first step is to let Ente know about the domain or subdomain you wish to use by linking it to your account.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Currently (Aug 2025) the ability to link a custom domain is only present in Ente's web app, [web.ente.io](https://web.ente.io). It will come to Ente mobile and desktop when their next versions get released.
|
||||
|
||||
Head over to Preferences > Custom domains, in the domain field enter "pics.example.org" (replace with your subdomain) and press "Save". That's it. The linking is done.
|
||||
|
||||
### Step 2 - Add DNS entry
|
||||
|
||||
The second step is to add a CNAME entry in your DNS provider that forwards requests for pics.example.org (replace with your subdomain) to **my.ente.io**.
|
||||
|
||||
Specifically, you need to add a `CNAME record` from the domain (or subdomain) of your choice to `my.ente.io`. You can leave the `TTL` at its default.
|
||||
|
||||
| Record Type | Name | Value | TTL |
|
||||
| ----------- | :------------------------: | -----------: | -------------- |
|
||||
| CNAME | Your subdomain, e.g `pics` | `my.ente.io` | Auto (default) |
|
||||
|
||||
The exact steps for doing this depend on the DNS provider that you're using.
|
||||
|
||||
> Your DNS provider usually is the service from which you bought your domain. The domain name seller will provide some sort of an admin panel where you can configure your DNS settings.
|
||||
|
||||
As concrete examples, here is how this step would look for Cloudflare:
|
||||
|
||||

|
||||
|
||||
Note that orange proxy option is off. And here is how it would look for Namecheap:
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> The examples are using "pics" as the subdomain, but that's just an example, you can use anything you like (or use "@" if you'd like to use the root domain itself).
|
||||
|
||||
The time it takes for DNS records to update is dependent on your DNS provider. Usually the changes should start reflecting within a few minutes, and should almost always reflect within an hour.
|
||||
|
||||
Once the DNS changes have been applied, then you can take any public link to your shared albums, replace `albums.ente.io` with your choice (e.g. `pics.example.org`), and the link will still work.
|
||||
|
||||
You don't need to do this manually though, the apps will do it for you. More on this in the next section. But first, some troubleshooting tips.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
If your domain is not working, go through the following checklist.
|
||||
|
||||
- The CNAME should be from your domain to my.ente.io, not the other way around. That is, `pics.example.org => my.ente.io`.
|
||||
|
||||
- If you're using Cloudflare DNS, make sure that the "Orange" proxy status toggle is off, and the Proxy status is the "Grey" DNS only.
|
||||
|
||||
## Using
|
||||
|
||||
Using is trivial. When you go to an album's sharing options and copy the link to it, Ente will automatically copy the link that uses your configured domain.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Currently (Aug 2025) the ability to automatically substitute your custom domain is only present in Ente's web app, [web.ente.io](https://web.ente.io). It will come to Ente mobile and desktop when their next versions get released.
|
||||
|
||||
## Unsetting
|
||||
|
||||
To stop using your custom domain, we need to undo the two steps we did during setup.
|
||||
|
||||
1. Unlink your domain in Ente. This can be done just by going to Preferences > Custom Domains, clearing the value in the "Domain" input and pressing "Update".
|
||||
|
||||
2. Remove the CNAME record you added during setup in your DNS provider.
|
||||
BIN
docs/docs/photos/features/custom-domains/nc.png
Normal file
BIN
docs/docs/photos/features/custom-domains/nc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
@@ -112,4 +112,4 @@ ip addr add 10.10.10.1/24 dev dummy0
|
||||
ip link set dummy0 up
|
||||
```
|
||||
|
||||
Once the interface is up, Ente correctly detects that the system is online.
|
||||
Once the interface is up, Ente correctly detects that the system is online.
|
||||
|
||||
@@ -30,11 +30,13 @@ if (keystorePropertiesFile.exists()) {
|
||||
|
||||
android {
|
||||
namespace "io.ente.auth"
|
||||
compileSdk 35
|
||||
compileSdk 36
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled = true
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
// Sets Java compatibility to Java 8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -57,8 +59,8 @@ android {
|
||||
applicationId "io.ente.auth"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion 22
|
||||
targetSdkVersion 34
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -117,5 +119,6 @@ flutter {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// For AGP 7.4+
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
|
||||
@@ -19,8 +19,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.2.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
id "com.android.application" version "8.6.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
@@ -1358,6 +1358,13 @@
|
||||
"r10.net"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "RaiderIO",
|
||||
"slug": "raider_io",
|
||||
"altNames": [
|
||||
"raider.io"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Raindrop.io",
|
||||
"slug": "raindrop_io",
|
||||
@@ -1501,6 +1508,10 @@
|
||||
{
|
||||
"title": "Skinport"
|
||||
},
|
||||
{
|
||||
"title": "Skyscanner",
|
||||
"hex": "0770E3"
|
||||
},
|
||||
{
|
||||
"title": "SMSPool",
|
||||
"slug": "sms_pool_net",
|
||||
|
||||
17
mobile/apps/auth/assets/custom-icons/icons/raider_io.svg
Normal file
17
mobile/apps/auth/assets/custom-icons/icons/raider_io.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="katman_1" xmlns="http://www.w3.org/2000/svg" baseProfile="tiny" version="1.2" viewBox="0 0 295.13 303">
|
||||
<!-- Generator: Adobe Illustrator 29.6.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 207) -->
|
||||
<g id="Logo_2ColorWhite">
|
||||
<path d="M453,42.76h-59.41v218.34h32.58v-76h26.83c18.81,0,26.83,5.13,26.83,15.81v60.2h32.59v-60.21c0-18.45-8.75-27.41-15.75-31.17,7-3.85,15.75-10.31,15.75-28.75v-52.07c0-21.05-10.31-46.15-59.42-46.15ZM479.83,140.98c0,10.67-8,13.18-26.83,13.18h-26.83v-80.47h26.83c18.81,0,26.83,4.54,26.83,15.22v52.07Z" fill="#fff"/>
|
||||
<path d="M607.59,39.5c-39.84,0-62.73,18-62.73,49.41v172.19h32.58v-76h60.31v76h32.58V88.91c-.01-31.41-22.88-49.41-62.74-49.41ZM637.75,154.15h-60.3v-65.24c0-13,9-18.54,30.15-18.54s30.15,5.54,30.15,18.54v65.24Z" fill="#fff"/>
|
||||
<polygon points="702.87 73.69 716.14 73.69 716.14 230.17 702.87 230.17 702.87 261.1 761.99 261.1 761.99 230.17 748.72 230.17 748.72 73.69 761.99 73.69 761.99 42.76 702.87 42.76 702.87 73.69" fill="#fff"/>
|
||||
<path d="M857.66,42.76h-62.73v218.34h62.73c39.87,0,62.74-18,62.74-49.46v-119.42c0-31.42-22.87-49.46-62.74-49.46ZM887.8,211.64c0,13-9,18.53-30.14,18.53h-30.14V73.69h30.14c21.14,0,30.14,5.54,30.14,18.53v119.42Z" fill="#fff"/>
|
||||
<path d="M953.52,92.22v119.42c0,31.42,22.87,49.46,62.74,49.46h49.49v-30.93h-49.45c-21.14,0-30.16-5.54-30.16-18.53v-40.65h73v-30.93h-73v-47.84c0-13,9-18.53,30.16-18.53h49.45v-30.93h-49.45c-39.91,0-62.78,18.04-62.78,49.46Z" fill="#fff"/>
|
||||
<path d="M1158.23,42.81h-59.42v218.34h32.6v-76.36h26.82c18.81,0,26.84,4.55,26.84,15.22v61.14h32.58v-61.14c0-18.44-8.75-26.82-15.74-30.57,7-3.85,15.74-12.35,15.74-30.79v-49.69c0-21.05-10.32-46.15-59.42-46.15ZM1185.07,138.65c0,10.67-8,15.22-26.84,15.22h-26.82v-80.13h26.82c18.81,0,26.84,4.54,26.84,15.22v49.69Z" fill="#fff"/>
|
||||
<rect x="1250.28" y="201.98" width="32.58" height="59.12" fill="#fff"/>
|
||||
<polygon points="1315.82 73.69 1329.08 73.69 1329.08 230.17 1315.82 230.17 1315.82 261.1 1374.93 261.1 1374.93 230.17 1361.67 230.17 1361.67 73.69 1374.93 73.69 1374.93 42.76 1315.82 42.76 1315.82 73.69" fill="#fff"/>
|
||||
<path d="M1470.75,39.5c-39.85,0-62.72,18-62.72,49.46v126c0,31.43,22.87,49.45,62.72,49.45s62.74-18,62.74-49.45v-126c-.03-31.46-22.9-49.46-62.74-49.46ZM1500.9,215.01c0,13-9,18.53-30.15,18.53s-30.14-5.54-30.14-18.53v-126c0-13,9-18.54,30.14-18.54s30.15,5.54,30.15,18.54v126Z" fill="#fff"/>
|
||||
<path d="M292.75,118.28c1.83-2.01,2.65-4.74,2.23-7.43-.21-.81-.41-1.63-.63-2.44-.97-2.59-3.07-4.58-5.7-5.42l-28-8.45c-6.93-2.1-11.04-9.23-9.39-16.28l6.67-28.47c.56-2.32.21-4.77-1-6.83-.81-1.37-1.96-2.51-3.33-3.32-2.06-1.21-4.5-1.57-6.82-1l-28.47,6.67c-7.05,1.66-14.18-2.46-16.27-9.39l-8.46-28c-.83-2.57-2.76-4.64-5.28-5.63-.91-.25-1.83-.46-2.74-.7-2.63-.37-5.3.45-7.27,2.23l-21.32,20c-5.28,4.96-13.51,4.96-18.79,0L116.86,3.82c-1.97-1.78-4.64-2.6-7.27-2.23-.91.24-1.83.45-2.73.7-2.52.99-4.46,3.05-5.29,5.63l-8.46,28c-2.09,6.94-9.22,11.05-16.27,9.39l-28.47-6.71c-2.32-.56-4.77-.21-6.83,1-1.37.81-2.52,1.95-3.33,3.32-1.21,2.06-1.56,4.51-1,6.83l6.67,28.48c1.65,7.06-2.48,14.19-9.43,16.27l-28,8.45c-2.64.85-4.74,2.86-5.7,5.46-.22.81-.42,1.63-.63,2.44-.42,2.69.4,5.42,2.23,7.43l20,21.32c4.96,5.28,4.96,13.51,0,18.79l-20,21.32c-1.86,2.04-2.69,4.82-2.23,7.55.2.74.38,1.49.58,2.23.96,2.63,3.08,4.67,5.75,5.51l28,8.46c6.93,2.09,11.05,9.22,9.39,16.27l-6.67,28.47c-.6,2.56-.07,5.25,1.46,7.39,2.17,3.13,6.02,4.64,9.74,3.8l47.08-5.1c2.39-.24,4.14-2.36,3.93-4.76-1.57-17.27-9-81.84-11.73-105.94-.66-5.76,1.5-11.49,5.8-15.39l46.11-42c4.53-4.13,11.47-4.13,16,0l46.05,42c4.29,3.89,6.45,9.61,5.79,15.37-2.75,24.08-10.09,88.68-11.65,106-.21,2.39,1.54,4.51,3.93,4.75l47.09,5.1c2.58.61,5.29.06,7.43-1.49.9-.63,1.67-1.4,2.3-2.3,1.53-2.14,2.06-4.84,1.46-7.4l-6.67-28.47c-1.65-7.05,2.46-14.17,9.39-16.26l28-8.46c2.67-.84,4.79-2.88,5.75-5.51.2-.74.38-1.49.58-2.24.45-2.72-.37-5.5-2.23-7.54l-20-21.32c-4.96-5.28-4.96-13.51,0-18.79l19.97-21.36Z" fill="#e5a024"/>
|
||||
<path d="M152.13,125.06c-2.15-2.52-5.94-2.81-8.46-.66-.24.2-.46.42-.66.66l-15.81,18.18c-3.17,3.64-4.78,8.38-4.49,13.2l8,135.87c.33,4.92,4.32,8.8,9.25,9,2.5.13,5.02.19,7.56.2,2.54,0,5.07-.06,7.6-.2,4.93-.19,8.93-4.07,9.25-9l8.11-135.81c.28-4.83-1.34-9.57-4.51-13.22l-15.84-18.22Z" fill="#808080"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-376.452 18.166 399.284 251.868"><path d="M195.1 220c2.1 0 4.1-.5 6-1.6l21.9-12.6c4.4-2.5 9.5-3.6 14.6-3 26.6 3.1 45.2 8.1 50.7 9.7 1.1.3 2.4-.1 3.1-1 .9-1.1 2-2.9 2.9-5.5.8-2.5.9-4.6.8-6.1-.1-1.2-.9-2.3-2.1-2.6-8.6-2.5-46.7-12.8-97.9-12.8s-89.3 10.3-97.9 12.8c-1.2.3-2 1.4-2.1 2.6-.1 1.4 0 3.5.8 6.1.8 2.6 2 4.4 2.9 5.5.7.9 2 1.3 3.1 1 5.5-1.6 24.2-6.6 50.7-9.7 5.1-.6 10.2.5 14.6 3l21.9 12.6c1.9 1.1 4 1.6 6 1.6zM158.6 149.1c1.2 2.1 3.1 3.5 5.3 4.1 2.2.6 4.5.3 6.6-.9 2.1-1.2 3.5-3.1 4.1-5.3.6-2.2.3-4.5-.9-6.6L154 106.3c-.6-1.1-1.9-1.5-3.1-1.4-1.6.1-3.8.9-6.4 2.4-2.6 1.5-4.4 3-5.3 4.3-.7 1-.9 2.3-.3 3.4l19.7 34.1zM140.6 173.5c2.1 1.2 4.5 1.4 6.6.9 2.2-.6 4.1-2 5.3-4.1 1.2-2.1 1.4-4.5.9-6.6-.5-2.1-2-4.1-4.1-5.3l-34.1-19.7c-1.1-.6-2.4-.4-3.4.3-1.3.9-2.8 2.7-4.3 5.3-1.5 2.6-2.3 4.8-2.4 6.4-.1 1.3.4 2.5 1.4 3.1l34.1 19.7zM203.8 137c0 2.4-1 4.6-2.5 6.2-1.6 1.6-3.7 2.5-6.2 2.5-2.4 0-4.6-1-6.2-2.5-1.6-1.6-2.5-3.7-2.5-6.2V97.7c0-1.3.8-2.3 2-2.8 1.4-.7 3.7-1.1 6.7-1.1s5.3.4 6.7 1.1c1.1.6 2 1.5 2 2.8V137zM231.6 149.1c-1.2 2.1-3.1 3.5-5.3 4.1-2.2.6-4.5.3-6.6-.9-2.1-1.2-3.5-3.1-4.1-5.3-.6-2.2-.3-4.5.9-6.6l19.7-34.1c.6-1.1 1.9-1.5 3.1-1.4 1.6.1 3.8.9 6.4 2.4 2.6 1.5 4.4 3 5.3 4.3.7 1 .9 2.3.3 3.4l-19.7 34.1zM249.6 173.5c-2.1 1.2-4.5 1.4-6.6.9-2.2-.6-4.1-2-5.3-4.1-1.2-2.1-1.4-4.5-.9-6.6.6-2.2 2-4.1 4.1-5.3l34.1-19.7c1.1-.6 2.4-.4 3.4.3 1.3.9 2.8 2.7 4.3 5.3 1.5 2.6 2.3 4.8 2.4 6.4.1 1.3-.4 2.5-1.4 3.1l-34.1 19.7z" class="st0" style="fill:#0770e3" transform="translate(-566.187 -169.038) scale(1.99578)"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -59,14 +59,14 @@ class PlatformUtil {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
await FileSaver.instance.saveAs(
|
||||
name: fileName,
|
||||
ext: extension,
|
||||
fileExtension: extension,
|
||||
bytes: bytes,
|
||||
mimeType: type,
|
||||
);
|
||||
} else {
|
||||
await FileSaver.instance.saveFile(
|
||||
name: fileName,
|
||||
ext: extension,
|
||||
fileExtension: extension,
|
||||
bytes: bytes,
|
||||
mimeType: type,
|
||||
);
|
||||
|
||||
@@ -540,10 +540,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_saver
|
||||
sha256: "448b1e30142cffe52f37ee085ea9ca50670d5425bb09b649d193549b2dcf6e26"
|
||||
sha256: "9d93db09bd4da9e43238f9dd485360fc51a5c138eea5ef5f407ec56e58079ac0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "0.3.1"
|
||||
fixnum:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -555,10 +555,11 @@ packages:
|
||||
fk_user_agent:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: fk_user_agent
|
||||
sha256: fd6c94e120786985a292d12f61422a581f4e851148d5940af38b819357b8ad0d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
path: "."
|
||||
ref: "458046cd9a88924e5074d96ba45397219d53b230"
|
||||
resolved-ref: "458046cd9a88924e5074d96ba45397219d53b230"
|
||||
url: "https://github.com/flutter-fast-kit/fk_user_agent"
|
||||
source: git
|
||||
version: "2.1.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
@@ -1145,9 +1146,9 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: v2-only
|
||||
resolved-ref: "0cdfeed654d79636eff0c57110f3f6ad5801ba2f"
|
||||
url: "https://github.com/ente-io/move_to_background.git"
|
||||
ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d"
|
||||
resolved-ref: "91e4d1a9c55b28bf93425d1f12faf410efc1e48d"
|
||||
url: "https://github.com/Sayegh7/move_to_background"
|
||||
source: git
|
||||
version: "1.0.2"
|
||||
native_dio_adapter:
|
||||
@@ -1393,11 +1394,12 @@ packages:
|
||||
qr_code_scanner_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_code_scanner_plus
|
||||
sha256: "39696b50d277097ee4d90d4292de36f38c66213a4f5216a06b2bdd2b63117859"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.10+1"
|
||||
path: "."
|
||||
ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
resolved-ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
url: "https://github.com/juliuscanute/qr_code_scanner"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 4.4.6+446
|
||||
@@ -8,12 +7,12 @@ environment:
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
adaptive_theme: ^3.1.0 # done
|
||||
adaptive_theme: ^3.1.0
|
||||
app_links: ^6.3.3
|
||||
archive: ^4.0.7
|
||||
auto_size_text: ^3.0.0
|
||||
base32: ^2.1.3
|
||||
bip39: ^1.0.6 #done
|
||||
bip39: ^1.0.6
|
||||
bloc: ^9.0.0
|
||||
clipboard: ^0.1.3
|
||||
collection: ^1.18.0 # dart
|
||||
@@ -52,10 +51,12 @@ dependencies:
|
||||
ffi: ^2.1.0
|
||||
figma_squircle: ^0.6.3
|
||||
file_picker: ^10.2.0
|
||||
# https://github.com/incrediblezayed/file_saver/issues/86
|
||||
file_saver: ^0.3.0
|
||||
file_saver: ^0.3.1
|
||||
fixnum: ^1.1.0
|
||||
fk_user_agent: ^2.1.0
|
||||
fk_user_agent: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/flutter-fast-kit/fk_user_agent
|
||||
ref: 458046cd9a88924e5074d96ba45397219d53b230
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_animate: ^4.1.0
|
||||
@@ -67,7 +68,7 @@ dependencies:
|
||||
# https://github.com/pichillilorenzo/flutter_inappwebview/pull/2548
|
||||
flutter_inappwebview: ^6.1.4
|
||||
flutter_launcher_icons: ^0.14.1
|
||||
flutter_local_authentication:
|
||||
flutter_local_authentication: # linux fprintd fix is not published on pub.dev
|
||||
git:
|
||||
url: https://github.com/eaceto/flutter_local_authentication
|
||||
ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96
|
||||
@@ -91,10 +92,10 @@ dependencies:
|
||||
local_auth_darwin: ^1.2.2
|
||||
logging: ^1.0.1
|
||||
modal_bottom_sheet: ^3.0.0
|
||||
move_to_background: # no updates in git, replace package
|
||||
move_to_background: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/ente-io/move_to_background.git
|
||||
ref: v2-only
|
||||
url: https://github.com/Sayegh7/move_to_background
|
||||
ref: 91e4d1a9c55b28bf93425d1f12faf410efc1e48d
|
||||
native_dio_adapter: ^1.4.0
|
||||
otp: ^3.1.1
|
||||
package_info_plus: ^8.0.2
|
||||
@@ -105,8 +106,11 @@ dependencies:
|
||||
pointycastle: ^3.7.3
|
||||
privacy_screen: ^0.0.6
|
||||
protobuf: ^4.1.0
|
||||
qr_code_scanner_plus: ^2.0.10+1
|
||||
qr_flutter: ^4.1.0
|
||||
qr_code_scanner: # no package updates on pub.dev
|
||||
git:
|
||||
url: https://github.com/juliuscanute/qr_code_scanner
|
||||
ref: a2d31633d4744f72ada87cfa85d221358ab082af
|
||||
qr_flutter: ^4.1.0
|
||||
sentry: ^8.14.2
|
||||
sentry_flutter: ^8.14.2
|
||||
share_plus: ^11.0.0
|
||||
|
||||
@@ -46,25 +46,27 @@ You can alternatively install the build from PlayStore or F-Droid.
|
||||
|
||||
## 🧑💻 Building from source
|
||||
|
||||
1. [Install Flutter v3.32.8](https://flutter.dev/docs/get-started/install).
|
||||
1. Install [Flutter v3.32.8](https://flutter.dev/docs/get-started/install) and [Rust](https://www.rust-lang.org/tools/install).
|
||||
|
||||
2. Pull in all submodules with `git submodule update --init --recursive`
|
||||
2. Install [Flutter Rust Bridge](https://cjycode.com/flutter_rust_bridge/) with `cargo install flutter_rust_bridge_codegen`
|
||||
|
||||
3. Enable repo git hooks `git config core.hooksPath hooks`
|
||||
3. Pull in all submodules with `git submodule update --init --recursive`
|
||||
|
||||
4. If using Visual Studio Code, add the [Flutter
|
||||
4. Enable repo git hooks `git config core.hooksPath hooks`
|
||||
|
||||
5. If using Visual Studio Code, add the [Flutter
|
||||
Intl](https://marketplace.visualstudio.com/items?itemName=localizely.flutter-intl)
|
||||
extension
|
||||
|
||||
5. On Android:
|
||||
6. On Android:
|
||||
|
||||
* For development, run `flutter run -t lib/main.dart --flavor independent`
|
||||
- For development, run `flutter run -t lib/main.dart --flavor independent`
|
||||
|
||||
* For building APK, [setup your
|
||||
- For building APK, [setup your
|
||||
keystore](https://docs.flutter.dev/deployment/android#create-an-upload-keystore)
|
||||
and run `flutter build apk --release --flavor independent`
|
||||
|
||||
6. For iOS, run `flutter build ios`
|
||||
7. For iOS, run `flutter build ios`
|
||||
|
||||
Some common issues and troubleshooting tips are in [docs/dev](docs/dev.md).
|
||||
|
||||
@@ -88,11 +90,12 @@ issue](https://github.com/ente-io/ente/issues/new?title=Request+for+New+Language
|
||||
to have it added.
|
||||
|
||||
## Certificate Fingerprints
|
||||
|
||||
|
||||
- **SHA1**: E1:60:10:18:B6:B0:2E:A3:74:6F:90:67:50:30:29:75:0E:EF:6D:39
|
||||
- **SHA256**: 35:ED:56:81:B7:0B:B3:BD:35:D9:0D:85:6A:F5:69:4C:50:4D:EF:46:AA:D8:3F:77:7B:1C:67:5C:F4:51:35:0B
|
||||
|
||||
To verify these fingerprints, use the following command:
|
||||
|
||||
```bash
|
||||
apksigner verify --print-certs <path_to_apk>
|
||||
```
|
||||
|
||||
@@ -30,8 +30,8 @@ if (keystorePropertiesFile.exists()) {
|
||||
|
||||
android {
|
||||
namespace = "io.ente.photos"
|
||||
compileSdk = 35
|
||||
ndkVersion = flutter.ndkVersion
|
||||
compileSdk = 36
|
||||
ndkVersion = "28.0.13004108"
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled = true
|
||||
@@ -56,7 +56,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "io.ente.photos"
|
||||
minSdk = 26
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
targetSdk = 35
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
@@ -153,4 +153,4 @@ dependencies {
|
||||
because("Align work-runtime-ktx versions")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3530
mobile/apps/photos/assets/ml/text_embeddings.json
Normal file
3530
mobile/apps/photos/assets/ml/text_embeddings.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ FITUR
|
||||
- Collaborative albums, so you can pool together photos after a trip
|
||||
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
|
||||
- Link album, yang bisa dilindungi dengan sandi
|
||||
- Ability to free up space, by removing files that have been safely backed up
|
||||
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
|
||||
- Editor gambar, untuk menyempurnakan fotomu
|
||||
@@ -33,4 +33,4 @@ 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.
|
||||
Kita bangga untuk menyediakan support berbasis manusia. Jika kamu adalah pelanggan berbayar, kamu bisa menghubungi team@ente.io dan menunggu balasan dari tim kami dalam 24 jam.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
ente is a simple app to backup and share your photos and videos.
|
||||
|
||||
If you've been looking for a privacy-friendly alternative to Google Photos, you've come to the right place. With ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
|
||||
|
||||
We have open-source apps across Android, iOS, web and desktop, and your photos will seamlessly sync between all of them in an end-to-end encrypted (e2ee) manner.
|
||||
|
||||
ente also makes it simple to share your albums with your loved ones, even if they aren't on ente. You can share publicly viewable links, where they can view your album and collaborate by adding photos to it, even without an account or app.
|
||||
|
||||
Your encrypted data is replicated to 3 different locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
|
||||
|
||||
We are here to make the safest photos app ever, come join our journey!
|
||||
|
||||
FEATURES
|
||||
- Original quality backups, because every pixel is important
|
||||
- Family plans, so you can share storage with your family
|
||||
- Collaborative albums, so you can pool together photos after a trip
|
||||
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
|
||||
- Album links, that can be protected with a password
|
||||
- Ability to free up space, by removing files that have been safely backed up
|
||||
- Human support, because you're worth it
|
||||
- Descriptions, so you can caption your memories and find them easily
|
||||
- Image editor, to add finishing touches
|
||||
- Favorite, hide and relive your memories, for they are precious
|
||||
- One-click import from Google, Apple, your hard drive and more
|
||||
- Dark theme, because your photos look good in it
|
||||
- 2FA, 3FA, biometric auth
|
||||
- and a LOT more!
|
||||
|
||||
PERMISSIONS
|
||||
ente requests for certain permissions to serve the purpose of a photo storage provider, which can be reviewed here: https://github.com/ente-io/ente/blob/f-droid/mobile/android/permissions.md
|
||||
|
||||
PRICING
|
||||
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
|
||||
|
||||
SUPPORT
|
||||
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
|
||||
@@ -0,0 +1 @@
|
||||
ente is an end-to-end encrypted photo storage app
|
||||
@@ -0,0 +1 @@
|
||||
ente - encrypted photo storage
|
||||
@@ -1 +1 @@
|
||||
ente is an end-to-end encrypted photo storage app
|
||||
ente är en end-to-end-krypterad fotolagringsapp
|
||||
@@ -1 +1 @@
|
||||
ente - encrypted photo storage
|
||||
ente - krypterad fotolagring
|
||||
33
mobile/apps/photos/fastlane/metadata/ios/nn/description.txt
Normal file
33
mobile/apps/photos/fastlane/metadata/ios/nn/description.txt
Normal file
@@ -0,0 +1,33 @@
|
||||
Ente is a simple app to automatically backup and organize your photos and videos.
|
||||
|
||||
If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
|
||||
|
||||
We have apps across all platforms, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
|
||||
|
||||
Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
|
||||
|
||||
Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
|
||||
|
||||
We are here to make the safest photos app ever, come join our journey!
|
||||
|
||||
FEATURES
|
||||
- Original quality backups, because every pixel is important
|
||||
- Family plans, so you can share storage with your family
|
||||
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
|
||||
- Album links, that can be protected with a password and set to expire
|
||||
- Ability to free up space, by removing files that have been safely backed up
|
||||
- Image editor, to add finishing touches
|
||||
- Favorite, hide and relive your memories, for they are precious
|
||||
- One-click import from all major storage providers
|
||||
- Dark theme, because your photos look good in it
|
||||
- 2FA, 3FA, biometric auth
|
||||
- and a LOT more!
|
||||
|
||||
PRICING
|
||||
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
|
||||
|
||||
SUPPORT
|
||||
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
|
||||
|
||||
TERMS
|
||||
https://ente.io/terms
|
||||
1
mobile/apps/photos/fastlane/metadata/ios/nn/keywords.txt
Normal file
1
mobile/apps/photos/fastlane/metadata/ios/nn/keywords.txt
Normal file
@@ -0,0 +1 @@
|
||||
photos,photography,family,privacy,cloud,backup,videos,photo,encryption,storage,album,alternative
|
||||
1
mobile/apps/photos/fastlane/metadata/ios/nn/name.txt
Normal file
1
mobile/apps/photos/fastlane/metadata/ios/nn/name.txt
Normal file
@@ -0,0 +1 @@
|
||||
Ente Photos
|
||||
1
mobile/apps/photos/fastlane/metadata/ios/nn/subtitle.txt
Normal file
1
mobile/apps/photos/fastlane/metadata/ios/nn/subtitle.txt
Normal file
@@ -0,0 +1 @@
|
||||
Encrypted photo storage
|
||||
@@ -1,33 +1,33 @@
|
||||
Ente är en enkel app för att automatiskt säkerhetskopiera och organisera dina foton och videor.
|
||||
Ente är en smidig app för att automatiskt säkerhetskopiera och organisera dina foton och videor.
|
||||
|
||||
Om du har letat efter ett integritetsvänligt alternativ för att bevara dina minnen, har du kommit rätt. Med Ente lagras de end-to-end-krypterat (e2ee). Det betyder att endast du kan se dem.
|
||||
|
||||
We have apps across all platforms, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
|
||||
Vi har appar på alla plattformar och dina foton synkroniseras sömlöst mellan alla dina enheter via end-to-end-kryptering (e2ee).
|
||||
|
||||
Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
|
||||
Ente gör det också enkelt att dela dina album med dina nära och kära. Du kan antingen dela dem direkt med andra Ente användare (end-to-end-krypterade), eller med publikt synliga länkar.
|
||||
|
||||
Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
|
||||
Din krypterade data lagras på flera platser, inklusive ett skyddsrum i Paris. Vi tar eftervärlden på allvar och gör det enkelt att se till att dina minnen överlever dig.
|
||||
|
||||
We are here to make the safest photos app ever, come join our journey!
|
||||
Vi är här för att skapa den säkraste foto-appen någonsin, kom och häng med på vår resa!
|
||||
|
||||
FEATURES
|
||||
- Original quality backups, because every pixel is important
|
||||
- Family plans, so you can share storage with your family
|
||||
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
|
||||
- Album links, that can be protected with a password and set to expire
|
||||
- Ability to free up space, by removing files that have been safely backed up
|
||||
- Image editor, to add finishing touches
|
||||
- Favorite, hide and relive your memories, for they are precious
|
||||
- One-click import from all major storage providers
|
||||
- Dark theme, because your photos look good in it
|
||||
- 2FA, 3FA, biometric auth
|
||||
- and a LOT more!
|
||||
FUNKTIONER
|
||||
- Säkerhetskopior av originalkvalitet, eftersom varje pixel är viktig
|
||||
- Familjeabonnemang, så att du kan dela lagring med din familj
|
||||
- Delade mappar, om du vill att din partner ska njuta av dina "Kamera-klick"
|
||||
- Albumlänkar, som kan skyddas med ett lösenord och automatiska förfallodatum
|
||||
- Möjlighet att frigöra utrymme, genom att ta bort filer som redan har säkerhetskopierats
|
||||
- Bildredigerare, för att lägga till en finishing touch
|
||||
- Favoritmarkera, göm, och återupplev dina värdefulla minnen
|
||||
- Import med ett klick från alla större lagringsleverantörer
|
||||
- Mörkt tema, eftersom dina bilder ser bra ut i det
|
||||
- 2FA, 3FA, biometrisk autentisering
|
||||
- och MYCKET mer!
|
||||
|
||||
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.
|
||||
PRISER
|
||||
Vi erbjuder inte gratisabonnemang för alltid, eftersom det är viktigt för oss att vi förblir hållbara och tål tidens prövning. Istället erbjuder vi prisvärda abonnemang som du fritt kan dela med din familj. Mer information hittar du på ente.io.
|
||||
|
||||
SUPPORT
|
||||
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
|
||||
Vi är stolta över att erbjuda support med riktiga personer. Om du är en betalande kund, kan du nå ut till team@ente.io och förvänta dig ett svar från vårt team inom 24 timmar.
|
||||
|
||||
VILLKOR
|
||||
https://ente.io/terms
|
||||
|
||||
@@ -1 +1 @@
|
||||
foto, fotografi, familj, integritet, moln, säkerhetskopia, videor, foto, kryptering, lagring, album, alternativ
|
||||
foton, fotografi, familj, integritet, moln, säkerhetskopia, videor, foto, kryptering, lagring, album, alternativ
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
Ente is a simple app to automatically backup and organize your photos and videos.
|
||||
|
||||
If you've been looking for a privacy-friendly alternative to preserve your memories, you've come to the right place. With Ente, they are stored end-to-end encrypted (e2ee). This means that only you can view them.
|
||||
|
||||
We have apps across Android, iOS, web and Desktop, and your photos will seamlessly sync between all your devices in an end-to-end encrypted (e2ee) manner.
|
||||
|
||||
Ente also makes it simple to share your albums with your loved ones. You can either share them directly with other Ente users, end-to-end encrypted; or with publicly viewable links.
|
||||
|
||||
Your encrypted data is stored across multiple locations, including a fall-out shelter in Paris. We take posterity seriously and make it easy to ensure that your memories outlive you.
|
||||
|
||||
We are here to make the safest photos app ever, come join our journey!
|
||||
|
||||
✨ FEATURES
|
||||
- Original quality backups, because every pixel is important
|
||||
- Family plans, so you can share storage with your family
|
||||
- Shared folders, in case you want your partner to enjoy your "Camera" clicks
|
||||
- Album links, that can be protected with a password and set to expire
|
||||
- Ability to free up space, by removing files that have been safely backed up
|
||||
- Image editor, to add finishing touches
|
||||
- Favorite, hide and relive your memories, for they are precious
|
||||
- One-click import from Google, Apple, your hard drive and more
|
||||
- Dark theme, because your photos look good in it
|
||||
- 2FA, 3FA, biometric auth
|
||||
- and a LOT more!
|
||||
|
||||
💲 PRICING
|
||||
We don't offer forever free plans, because it is important to us that we remain sustainable and withstand the test of time. Instead we offer affordable plans that you can freely share with your family. You can find more information at ente.io.
|
||||
|
||||
🙋 SUPPORT
|
||||
We take pride in offering human support. If you are our paid customer, you can reach out to team@ente.io and expect a response from our team within 24 hours.
|
||||
@@ -0,0 +1 @@
|
||||
Encrypted photo storage - backup, organize and share your photos and videos
|
||||
@@ -0,0 +1 @@
|
||||
Ente Photos
|
||||
@@ -1 +1 @@
|
||||
Encrypted photo storage - backup, organize and share your photos and videos
|
||||
Krypterad lagring av foton - säkerhetskopiera, organisera och dela dina foton och videor
|
||||
@@ -1 +1 @@
|
||||
Ente Photos
|
||||
Ente Foton
|
||||
8
mobile/apps/photos/flutter_rust_bridge.yaml
Normal file
8
mobile/apps/photos/flutter_rust_bridge.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
rust_input: crate::api
|
||||
rust_root: rust/
|
||||
dart_output: lib/src/rust
|
||||
|
||||
dart_preamble: |
|
||||
// ignore_for_file: require_trailing_commas
|
||||
|
||||
web: false
|
||||
@@ -91,10 +91,9 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
|
||||
///Use this widget as floating action buttom in HomeWidget so that frames
|
||||
///are built and rendered continuously so that timeline trace has continuous
|
||||
///data. Change the duraiton in `_startTimer()` to control the duraiton of
|
||||
///are built and rendered continuously so that timeline trace has continuous
|
||||
///data. Change the duraiton in `_startTimer()` to control the duraiton of
|
||||
///test on app init.
|
||||
|
||||
// class TempWidget extends StatefulWidget {
|
||||
@@ -127,4 +126,4 @@ Future<void> dismissUpdateAppDialog(WidgetTester tester) async {
|
||||
// ? const CircularProgressIndicator()
|
||||
// : const SizedBox.shrink();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
13
mobile/apps/photos/integration_test/simple_test.dart
Normal file
13
mobile/apps/photos/integration_test/simple_test.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import "package:photos/src/rust/api/simple.dart";
|
||||
import 'package:photos/src/rust/frb_generated.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
setUpAll(() async => await RustLib.init());
|
||||
testWidgets('Can call rust function', (WidgetTester tester) async {
|
||||
final testString = greet(name: "Tom");
|
||||
expect(testString.contains('Tom'), true);
|
||||
});
|
||||
}
|
||||
@@ -284,6 +284,7 @@ DEPENDENCIES:
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- rust_lib_photos (from `.symlinks/plugins/rust_lib_photos/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
@@ -407,6 +408,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/privacy_screen/ios"
|
||||
receive_sharing_intent:
|
||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||
rust_lib_photos:
|
||||
:path: ".symlinks/plugins/rust_lib_photos/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share_plus:
|
||||
|
||||
@@ -564,6 +564,7 @@
|
||||
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/rust_lib_photos/rust_lib_photos.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
|
||||
@@ -658,6 +659,7 @@
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rust_lib_photos.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import "dart:io";
|
||||
|
||||
import 'package:photos/core/cache/lru_map.dart';
|
||||
|
||||
@@ -24,7 +24,7 @@ class LRUMap<K, V> {
|
||||
final K evictedKey = _map.keys.first;
|
||||
final V? evictedValue = _map.remove(evictedKey);
|
||||
if (_handler != null) {
|
||||
_handler!(evictedKey, evictedValue);
|
||||
_handler(evictedKey, evictedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ bool isHandledSyncError(Object errObj) {
|
||||
|
||||
class LockAlreadyAcquiredError extends Error {}
|
||||
|
||||
class LockFreedError extends Error{}
|
||||
class LockFreedError extends Error {}
|
||||
|
||||
class UnauthorizedError extends Error {}
|
||||
|
||||
|
||||
311
mobile/apps/photos/lib/db/ml/clip_vector_db.dart
Normal file
311
mobile/apps/photos/lib/db/ml/clip_vector_db.dart
Normal file
@@ -0,0 +1,311 @@
|
||||
import "dart:io" show File;
|
||||
import "dart:typed_data" show Float32List;
|
||||
|
||||
import "package:flutter_rust_bridge/flutter_rust_bridge.dart" show Uint64List;
|
||||
import "package:logging/logging.dart";
|
||||
import "package:path/path.dart";
|
||||
import "package:path_provider/path_provider.dart";
|
||||
import "package:photos/models/ml/vector.dart";
|
||||
import "package:photos/services/machine_learning/semantic_search/query_result.dart";
|
||||
import "package:photos/src/rust/api/usearch_api.dart";
|
||||
|
||||
class ClipVectorDB {
|
||||
static final Logger _logger = Logger("ClipVectorDB");
|
||||
|
||||
static const _databaseName = "ente.ml.vectordb.clip";
|
||||
|
||||
static final BigInt _embeddingDimension = BigInt.from(512);
|
||||
|
||||
static Logger get logger => _logger;
|
||||
|
||||
// Singleton pattern
|
||||
ClipVectorDB._privateConstructor();
|
||||
static final instance = ClipVectorDB._privateConstructor();
|
||||
factory ClipVectorDB() => instance;
|
||||
|
||||
// only have a single app-wide reference to the database
|
||||
static Future<VectorDb>? _vectorDbFuture;
|
||||
|
||||
Future<VectorDb> get _vectorDB async {
|
||||
_vectorDbFuture ??= _initVectorDB();
|
||||
return _vectorDbFuture!;
|
||||
}
|
||||
|
||||
bool? _migrationDone;
|
||||
|
||||
Future<VectorDb> _initVectorDB() async {
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final String databaseDirectory =
|
||||
join(documentsDirectory.path, _databaseName);
|
||||
_logger.info("Opening vectorDB access: DB path " + databaseDirectory);
|
||||
final vectorDB = VectorDb(
|
||||
filePath: databaseDirectory,
|
||||
dimensions: _embeddingDimension,
|
||||
);
|
||||
final stats = await getIndexStats(vectorDB);
|
||||
_logger.info("VectorDB connection opened with stats: ${stats.toString()}");
|
||||
|
||||
return vectorDB;
|
||||
}
|
||||
|
||||
Future<bool> checkIfMigrationDone() async {
|
||||
if (_migrationDone != null) return _migrationDone!;
|
||||
_logger.info("Checking if ClipVectorDB migration has run");
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final migrationFlagFile =
|
||||
File(join(documentsDirectory.path, 'clip_vector_migration_done'));
|
||||
if (await migrationFlagFile.exists()) {
|
||||
_logger.info("ClipVectorDB migration already done");
|
||||
_migrationDone = true;
|
||||
return _migrationDone!;
|
||||
} else {
|
||||
_logger.info("ClipVectorDB migration not done");
|
||||
_migrationDone = false;
|
||||
return _migrationDone!;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setMigrationDone() async {
|
||||
_logger.info("Setting ClipVectorDB migration done");
|
||||
final documentsDirectory = await getApplicationDocumentsDirectory();
|
||||
final migrationFlagFile =
|
||||
File(join(documentsDirectory.path, 'clip_vector_migration_done'));
|
||||
await migrationFlagFile.create(recursive: true);
|
||||
_migrationDone = true;
|
||||
}
|
||||
|
||||
Future<void> insertEmbedding({
|
||||
required int fileID,
|
||||
required List<double> embedding,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
await db.addVector(key: BigInt.from(fileID), vector: embedding);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error inserting embedding", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkInsertEmbeddings({
|
||||
required List<int> fileIDs,
|
||||
required List<Float32List> embeddings,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
final bigKeys = Uint64List.fromList(fileIDs);
|
||||
try {
|
||||
await db.bulkAddVectors(keys: bigKeys, vectors: embeddings);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error bulk inserting embeddings", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<EmbeddingVector>> getEmbeddings(List<int> fileIDs) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final keys = Uint64List.fromList(fileIDs);
|
||||
final vectors = await db.bulkGetVectors(keys: keys);
|
||||
return List.generate(
|
||||
vectors.length,
|
||||
(index) => EmbeddingVector(
|
||||
fileID: fileIDs[index],
|
||||
embedding: vectors[index],
|
||||
),
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error getting embeddings", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteEmbeddings(List<int> fileIDs) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final deletedCount =
|
||||
await db.bulkRemoveVectors(keys: Uint64List.fromList(fileIDs));
|
||||
_logger.info(
|
||||
"Deleted $deletedCount embeddings, from ${fileIDs.length} keys",
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error bulk deleting specific embeddings", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAllEmbeddings() async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
await db.resetIndex();
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error deleting all embeddings", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteIndex() async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
await db.deleteIndex();
|
||||
_vectorDbFuture = null;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error deleting index", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<VectorDbStats> getIndexStats([VectorDb? db]) async {
|
||||
db ??= await _vectorDB;
|
||||
try {
|
||||
final stats = await db.getIndexStats();
|
||||
return VectorDbStats(
|
||||
size: stats.$1.toInt(),
|
||||
capacity: stats.$2.toInt(),
|
||||
dimensions: stats.$3.toInt(),
|
||||
fileSize: stats.$4.toInt(),
|
||||
memoryUsage: stats.$5.toInt(),
|
||||
expansionAdd: stats.$6.toInt(),
|
||||
expansionSearch: stats.$7.toInt(),
|
||||
);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error getting index stats", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(Uint64List, Float32List)> searchClosestVectors(
|
||||
List<double> query,
|
||||
int count, {
|
||||
bool exact = false,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final result = await db.searchVectors(
|
||||
query: query,
|
||||
count: BigInt.from(count),
|
||||
exact: exact,
|
||||
);
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error searching closest vectors", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(BigInt, double)> searchClosestVector(
|
||||
List<double> query, {
|
||||
bool exact = false,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final result =
|
||||
await db.searchVectors(query: query, count: BigInt.one, exact: exact);
|
||||
return (result.$1[0], result.$2[0]);
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error searching closest vector", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(List<Uint64List>, List<Float32List>)> bulkSearchVectors(
|
||||
List<Float32List> queries,
|
||||
BigInt count, {
|
||||
bool exact = false,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final result = await db.bulkSearchVectors(
|
||||
queries: queries,
|
||||
count: count,
|
||||
exact: exact,
|
||||
);
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error bulk searching vectors", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(Uint64List, List<Uint64List>, List<Float32List>)> bulkSearchWithKeys(
|
||||
Uint64List potentialKeys,
|
||||
BigInt count, {
|
||||
bool exact = false,
|
||||
}) async {
|
||||
final db = await _vectorDB;
|
||||
try {
|
||||
final result = await db.bulkSearchKeys(
|
||||
potentialKeys: potentialKeys,
|
||||
count: count,
|
||||
exact: exact,
|
||||
);
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Error bulk searching vectors with potential keys", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, List<QueryResult>>> computeBulkSimilarities(
|
||||
Map<String, List<double>> textQueryToEmbeddingMap,
|
||||
Map<String, double> minimumSimilarityMap,
|
||||
) async {
|
||||
try {
|
||||
final queryToResults = <String, List<QueryResult>>{};
|
||||
for (final MapEntry<String, List<double>> entry
|
||||
in textQueryToEmbeddingMap.entries) {
|
||||
final query = entry.key;
|
||||
final minimumSimilarity = minimumSimilarityMap[query]!;
|
||||
final textEmbedding = entry.value;
|
||||
final (potentialFileIDs, distances) =
|
||||
await searchClosestVectors(textEmbedding, 1000);
|
||||
final queryResults = <QueryResult>[];
|
||||
for (var i = 0; i < potentialFileIDs.length; i++) {
|
||||
final similarity = 1 - distances[i];
|
||||
if (similarity >= minimumSimilarity) {
|
||||
queryResults
|
||||
.add(QueryResult(potentialFileIDs[i].toInt(), similarity));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
queryToResults[query] = queryResults;
|
||||
}
|
||||
return queryToResults;
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Could not bulk find embeddings similarities using vector DB",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VectorDbStats {
|
||||
final int size;
|
||||
final int capacity;
|
||||
final int dimensions;
|
||||
|
||||
// in bytes
|
||||
final int fileSize;
|
||||
final int memoryUsage;
|
||||
|
||||
final int expansionAdd;
|
||||
final int expansionSearch;
|
||||
|
||||
VectorDbStats({
|
||||
required this.size,
|
||||
required this.capacity,
|
||||
required this.dimensions,
|
||||
required this.fileSize,
|
||||
required this.memoryUsage,
|
||||
required this.expansionAdd,
|
||||
required this.expansionSearch,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "VectorDbStats(size: $size, capacity: $capacity, dimensions: $dimensions, file size on disk (bytes): $fileSize, memory usage (bytes): $memoryUsage, expansionAdd: $expansionAdd, expansionSearch: $expansionSearch)";
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/db/common/base.dart";
|
||||
import "package:photos/db/ml/base.dart";
|
||||
import "package:photos/db/ml/clip_vector_db.dart";
|
||||
import "package:photos/db/ml/db_model_mappers.dart";
|
||||
import 'package:photos/db/ml/schema.dart';
|
||||
import "package:photos/events/embedding_updated_event.dart";
|
||||
@@ -18,6 +19,7 @@ import "package:photos/models/ml/face/face.dart";
|
||||
import "package:photos/models/ml/face/face_with_embedding.dart";
|
||||
import "package:photos/models/ml/ml_versions.dart";
|
||||
import "package:photos/models/ml/vector.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart";
|
||||
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
|
||||
import "package:photos/services/machine_learning/ml_result.dart";
|
||||
@@ -1253,6 +1255,111 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
|
||||
return embeddings;
|
||||
}
|
||||
|
||||
Future<void> checkMigrateFillClipVectorDB({bool force = false}) async {
|
||||
final migrationDone = await ClipVectorDB.instance.checkIfMigrationDone();
|
||||
if (migrationDone && !force) {
|
||||
_logger.info("ClipVectorDB migration not needed, already done");
|
||||
return;
|
||||
}
|
||||
_logger.info("Starting ClipVectorDB migration");
|
||||
|
||||
// Get total count first to track progress
|
||||
_logger.info("Getting total count of clip embeddings");
|
||||
final db = await instance.asyncDB;
|
||||
final countResult =
|
||||
await db.getAll('SELECT COUNT($fileIDColumn) as total FROM $clipTable');
|
||||
final totalCount = countResult.first['total'] as int;
|
||||
if (totalCount == 0) {
|
||||
_logger.info("No clip embeddings to migrate");
|
||||
await ClipVectorDB.instance.setMigrationDone();
|
||||
return;
|
||||
}
|
||||
_logger.info("Total count of clip embeddings: $totalCount");
|
||||
|
||||
_logger.info("First time referencing ClipVectorDB rust index in migration");
|
||||
final clipVectorDB = ClipVectorDB.instance;
|
||||
await clipVectorDB.deleteAllEmbeddings();
|
||||
_logger.info("ClipVectorDB rust index referenced");
|
||||
_logger.info("ClipVectorDB all embeddings cleared");
|
||||
|
||||
_logger
|
||||
.info("Starting migration of $totalCount clip embeddings to vector DB");
|
||||
const batchSize = 5000;
|
||||
int offset = 0;
|
||||
int processedCount = 0;
|
||||
int weirdCount = 0;
|
||||
int whileCount = 0;
|
||||
final stopwatch = Stopwatch()..start();
|
||||
try {
|
||||
while (true) {
|
||||
whileCount++;
|
||||
_logger.info("$whileCount st round of while loop");
|
||||
// Allow some time for any GC to finish
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
_logger.info("Reading $batchSize rows from DB");
|
||||
final List<Map<String, dynamic>> results = await db.getAll('''
|
||||
SELECT $fileIDColumn, $embeddingColumn
|
||||
FROM $clipTable
|
||||
ORDER BY $fileIDColumn DESC
|
||||
LIMIT $batchSize OFFSET $offset
|
||||
''');
|
||||
_logger.info("Got ${results.length} results from DB");
|
||||
if (results.isEmpty) {
|
||||
_logger.info("No more results, breaking out of while loop");
|
||||
break;
|
||||
}
|
||||
_logger.info("Processing ${results.length} results");
|
||||
final List<int> fileIDs = [];
|
||||
final List<Float32List> embeddings = [];
|
||||
for (final result in results) {
|
||||
final embedding =
|
||||
Float32List.view((result[embeddingColumn] as Uint8List).buffer);
|
||||
if (embedding.length == 512) {
|
||||
fileIDs.add(result[fileIDColumn] as int);
|
||||
embeddings.add(Float32List.view(result[embeddingColumn].buffer));
|
||||
} else {
|
||||
weirdCount++;
|
||||
}
|
||||
}
|
||||
_logger.info(
|
||||
"Got ${fileIDs.length} valid embeddings, $weirdCount weird embeddings",
|
||||
);
|
||||
|
||||
await ClipVectorDB.instance
|
||||
.bulkInsertEmbeddings(fileIDs: fileIDs, embeddings: embeddings);
|
||||
_logger.info("Inserted ${fileIDs.length} embeddings to ClipVectorDB");
|
||||
processedCount += fileIDs.length;
|
||||
offset += batchSize;
|
||||
_logger.info(
|
||||
"migrated $processedCount/$totalCount embeddings to ClipVectorDB",
|
||||
);
|
||||
if (processedCount >= totalCount) {
|
||||
_logger.info("All embeddings migrated, breaking out of while loop");
|
||||
break;
|
||||
}
|
||||
// Allow some time for any GC to finish
|
||||
_logger.info("Waiting for 100ms out of precaution, for GC to finish");
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
_logger.info(
|
||||
"migrated all $totalCount embeddings to ClipVectorDB in ${stopwatch.elapsed.inMilliseconds} ms, with $weirdCount weird embeddings not migrated",
|
||||
);
|
||||
await ClipVectorDB.instance.setMigrationDone();
|
||||
_logger.info("ClipVectorDB migration done, flag file created");
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Error migrating ClipVectorDB after ${stopwatch.elapsed.inMilliseconds} ms, clearing out DB again",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await clipVectorDB.deleteAllEmbeddings();
|
||||
rethrow;
|
||||
} finally {
|
||||
stopwatch.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// Get indexed FileIDs
|
||||
@override
|
||||
Future<Map<int, int>> clipIndexedFileWithVersion() async {
|
||||
@@ -1286,12 +1393,27 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
|
||||
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) VALUES (?, ?, ?)',
|
||||
_getRowFromEmbedding(embeddings.first),
|
||||
);
|
||||
if (flagService.enableVectorDb &&
|
||||
await ClipVectorDB.instance.checkIfMigrationDone()) {
|
||||
await ClipVectorDB.instance.insertEmbedding(
|
||||
fileID: embeddings.first.fileID,
|
||||
embedding: embeddings.first.embedding,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
final inputs = embeddings.map((e) => _getRowFromEmbedding(e)).toList();
|
||||
await db.executeBatch(
|
||||
'INSERT OR REPLACE INTO $clipTable ($fileIDColumn, $embeddingColumn, $mlVersionColumn) values(?, ?, ?)',
|
||||
inputs,
|
||||
);
|
||||
if (flagService.enableVectorDb &&
|
||||
await ClipVectorDB.instance.checkIfMigrationDone()) {
|
||||
await ClipVectorDB.instance.bulkInsertEmbeddings(
|
||||
fileIDs: embeddings.map((e) => e.fileID).toList(),
|
||||
embeddings:
|
||||
embeddings.map((e) => Float32List.fromList(e.embedding)).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Bus.instance.fire(EmbeddingUpdatedEvent());
|
||||
}
|
||||
@@ -1302,6 +1424,10 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
|
||||
await db.execute(
|
||||
'DELETE FROM $clipTable WHERE $fileIDColumn IN (${fileIDs.join(", ")})',
|
||||
);
|
||||
if (flagService.enableVectorDb &&
|
||||
await ClipVectorDB.instance.checkIfMigrationDone()) {
|
||||
await ClipVectorDB.instance.deleteEmbeddings(fileIDs);
|
||||
}
|
||||
Bus.instance.fire(EmbeddingUpdatedEvent());
|
||||
}
|
||||
|
||||
@@ -1309,6 +1435,10 @@ class MLDataDB with SqlDbBase implements IMLDataDB<int> {
|
||||
Future<void> deleteClipIndexes() async {
|
||||
final db = await instance.asyncDB;
|
||||
await db.execute('DELETE FROM $clipTable');
|
||||
if (flagService.enableVectorDb &&
|
||||
await ClipVectorDB.instance.checkIfMigrationDone()) {
|
||||
await ClipVectorDB.instance.deleteAllEmbeddings();
|
||||
}
|
||||
Bus.instance.fire(EmbeddingUpdatedEvent());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,27 +37,32 @@ class CenterBox extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
CenterBox._() : super();
|
||||
factory CenterBox.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory CenterBox.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory CenterBox.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory CenterBox.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CenterBox', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'CenterBox',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
|
||||
createEmptyInstance: create)
|
||||
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
|
||||
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
|
||||
..a<$core.double>(3, _omitFieldNames ? '' : 'height', $pb.PbFieldType.OF)
|
||||
..a<$core.double>(4, _omitFieldNames ? '' : 'width', $pb.PbFieldType.OF)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
CenterBox clone() => CenterBox()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
CenterBox copyWith(void Function(CenterBox) updates) => super.copyWith((message) => updates(message as CenterBox)) as CenterBox;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
CenterBox copyWith(void Function(CenterBox) updates) =>
|
||||
super.copyWith((message) => updates(message as CenterBox)) as CenterBox;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -66,13 +71,17 @@ class CenterBox extends $pb.GeneratedMessage {
|
||||
CenterBox createEmptyInstance() => create();
|
||||
static $pb.PbList<CenterBox> createRepeated() => $pb.PbList<CenterBox>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CenterBox getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CenterBox>(create);
|
||||
static CenterBox getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CenterBox>(create);
|
||||
static CenterBox? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.double get x => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set x($core.double v) { $_setFloat(0, v); }
|
||||
set x($core.double v) {
|
||||
$_setFloat(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasX() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
@@ -81,7 +90,10 @@ class CenterBox extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(2)
|
||||
$core.double get y => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set y($core.double v) { $_setFloat(1, v); }
|
||||
set y($core.double v) {
|
||||
$_setFloat(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasY() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
@@ -90,7 +102,10 @@ class CenterBox extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(3)
|
||||
$core.double get height => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set height($core.double v) { $_setFloat(2, v); }
|
||||
set height($core.double v) {
|
||||
$_setFloat(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasHeight() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
@@ -99,13 +114,16 @@ class CenterBox extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(4)
|
||||
$core.double get width => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set width($core.double v) { $_setFloat(3, v); }
|
||||
set width($core.double v) {
|
||||
$_setFloat(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasWidth() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearWidth() => clearField(4);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
|
||||
@@ -35,4 +35,3 @@ final $typed_data.Uint8List centerBoxDescriptor = $convert.base64Decode(
|
||||
'CglDZW50ZXJCb3gSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBARIbCgZoZW'
|
||||
'lnaHQYAyABKAJIAlIGaGVpZ2h0iAEBEhkKBXdpZHRoGAQgASgCSANSBXdpZHRoiAEBQgQKAl94'
|
||||
'QgQKAl95QgkKB19oZWlnaHRCCAoGX3dpZHRo');
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'box.pb.dart';
|
||||
|
||||
|
||||
@@ -29,25 +29,30 @@ class EPoint extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
EPoint._() : super();
|
||||
factory EPoint.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory EPoint.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory EPoint.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory EPoint.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EPoint', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'EPoint',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
|
||||
createEmptyInstance: create)
|
||||
..a<$core.double>(1, _omitFieldNames ? '' : 'x', $pb.PbFieldType.OF)
|
||||
..a<$core.double>(2, _omitFieldNames ? '' : 'y', $pb.PbFieldType.OF)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
EPoint clone() => EPoint()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EPoint copyWith(void Function(EPoint) updates) => super.copyWith((message) => updates(message as EPoint)) as EPoint;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EPoint copyWith(void Function(EPoint) updates) =>
|
||||
super.copyWith((message) => updates(message as EPoint)) as EPoint;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -56,13 +61,17 @@ class EPoint extends $pb.GeneratedMessage {
|
||||
EPoint createEmptyInstance() => create();
|
||||
static $pb.PbList<EPoint> createRepeated() => $pb.PbList<EPoint>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EPoint getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EPoint>(create);
|
||||
static EPoint getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EPoint>(create);
|
||||
static EPoint? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.double get x => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set x($core.double v) { $_setFloat(0, v); }
|
||||
set x($core.double v) {
|
||||
$_setFloat(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasX() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
@@ -71,13 +80,16 @@ class EPoint extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(2)
|
||||
$core.double get y => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set y($core.double v) { $_setFloat(1, v); }
|
||||
set y($core.double v) {
|
||||
$_setFloat(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasY() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearY() => clearField(2);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
|
||||
@@ -30,4 +30,3 @@ const EPoint$json = {
|
||||
final $typed_data.Uint8List ePointDescriptor = $convert.base64Decode(
|
||||
'CgZFUG9pbnQSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBAUIECgJfeEIECg'
|
||||
'JfeQ==');
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'point.pb.dart';
|
||||
|
||||
|
||||
@@ -26,24 +26,29 @@ class EVector extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
EVector._() : super();
|
||||
factory EVector.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory EVector.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory EVector.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory EVector.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'EVector', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'), createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'EVector',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.common'),
|
||||
createEmptyInstance: create)
|
||||
..p<$core.double>(1, _omitFieldNames ? '' : 'values', $pb.PbFieldType.KD)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
EVector clone() => EVector()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EVector copyWith(void Function(EVector) updates) => super.copyWith((message) => updates(message as EVector)) as EVector;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
EVector copyWith(void Function(EVector) updates) =>
|
||||
super.copyWith((message) => updates(message as EVector)) as EVector;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -52,13 +57,14 @@ class EVector extends $pb.GeneratedMessage {
|
||||
EVector createEmptyInstance() => create();
|
||||
static $pb.PbList<EVector> createRepeated() => $pb.PbList<EVector>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static EVector getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EVector>(create);
|
||||
static EVector getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<EVector>(create);
|
||||
static EVector? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.double> get values => $_getList(0);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
|
||||
@@ -22,6 +22,5 @@ const EVector$json = {
|
||||
};
|
||||
|
||||
/// Descriptor for `EVector`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List eVectorDescriptor = $convert.base64Decode(
|
||||
'CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');
|
||||
|
||||
final $typed_data.Uint8List eVectorDescriptor =
|
||||
$convert.base64Decode('CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');
|
||||
|
||||
@@ -11,4 +11,3 @@
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'vector.pb.dart';
|
||||
|
||||
|
||||
@@ -31,25 +31,32 @@ class Detection extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
Detection._() : super();
|
||||
factory Detection.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Detection.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory Detection.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory Detection.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Detection', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
|
||||
..aOM<$0.CenterBox>(1, _omitFieldNames ? '' : 'box', subBuilder: $0.CenterBox.create)
|
||||
..aOM<$1.EPoint>(2, _omitFieldNames ? '' : 'landmarks', subBuilder: $1.EPoint.create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Detection',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
|
||||
createEmptyInstance: create)
|
||||
..aOM<$0.CenterBox>(1, _omitFieldNames ? '' : 'box',
|
||||
subBuilder: $0.CenterBox.create)
|
||||
..aOM<$1.EPoint>(2, _omitFieldNames ? '' : 'landmarks',
|
||||
subBuilder: $1.EPoint.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Detection clone() => Detection()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Detection copyWith(void Function(Detection) updates) => super.copyWith((message) => updates(message as Detection)) as Detection;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Detection copyWith(void Function(Detection) updates) =>
|
||||
super.copyWith((message) => updates(message as Detection)) as Detection;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -58,13 +65,17 @@ class Detection extends $pb.GeneratedMessage {
|
||||
Detection createEmptyInstance() => create();
|
||||
static $pb.PbList<Detection> createRepeated() => $pb.PbList<Detection>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Detection getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Detection>(create);
|
||||
static Detection getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Detection>(create);
|
||||
static Detection? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$0.CenterBox get box => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set box($0.CenterBox v) { setField(1, v); }
|
||||
set box($0.CenterBox v) {
|
||||
setField(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasBox() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
@@ -75,7 +86,10 @@ class Detection extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(2)
|
||||
$1.EPoint get landmarks => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set landmarks($1.EPoint v) { setField(2, v); }
|
||||
set landmarks($1.EPoint v) {
|
||||
setField(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasLandmarks() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
@@ -103,26 +117,33 @@ class Face extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
Face._() : super();
|
||||
factory Face.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Face.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory Face.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory Face.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Face', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'Face',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'id')
|
||||
..aOM<Detection>(2, _omitFieldNames ? '' : 'detection', subBuilder: Detection.create)
|
||||
..a<$core.double>(3, _omitFieldNames ? '' : 'confidence', $pb.PbFieldType.OF)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..aOM<Detection>(2, _omitFieldNames ? '' : 'detection',
|
||||
subBuilder: Detection.create)
|
||||
..a<$core.double>(
|
||||
3, _omitFieldNames ? '' : 'confidence', $pb.PbFieldType.OF)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Face clone() => Face()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Face copyWith(void Function(Face) updates) => super.copyWith((message) => updates(message as Face)) as Face;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Face copyWith(void Function(Face) updates) =>
|
||||
super.copyWith((message) => updates(message as Face)) as Face;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -131,13 +152,17 @@ class Face extends $pb.GeneratedMessage {
|
||||
Face createEmptyInstance() => create();
|
||||
static $pb.PbList<Face> createRepeated() => $pb.PbList<Face>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Face getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Face>(create);
|
||||
static Face getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Face>(create);
|
||||
static Face? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get id => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set id($core.String v) { $_setString(0, v); }
|
||||
set id($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
@@ -146,7 +171,10 @@ class Face extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(2)
|
||||
Detection get detection => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set detection(Detection v) { setField(2, v); }
|
||||
set detection(Detection v) {
|
||||
setField(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasDetection() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
@@ -157,13 +185,16 @@ class Face extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(3)
|
||||
$core.double get confidence => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set confidence($core.double v) { $_setFloat(2, v); }
|
||||
set confidence($core.double v) {
|
||||
$_setFloat(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasConfidence() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearConfidence() => clearField(3);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
|
||||
@@ -17,8 +17,26 @@ import 'dart:typed_data' as $typed_data;
|
||||
const Detection$json = {
|
||||
'1': 'Detection',
|
||||
'2': [
|
||||
{'1': 'box', '3': 1, '4': 1, '5': 11, '6': '.ente.common.CenterBox', '9': 0, '10': 'box', '17': true},
|
||||
{'1': 'landmarks', '3': 2, '4': 1, '5': 11, '6': '.ente.common.EPoint', '9': 1, '10': 'landmarks', '17': true},
|
||||
{
|
||||
'1': 'box',
|
||||
'3': 1,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.ente.common.CenterBox',
|
||||
'9': 0,
|
||||
'10': 'box',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'landmarks',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.ente.common.EPoint',
|
||||
'9': 1,
|
||||
'10': 'landmarks',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_box'},
|
||||
@@ -37,8 +55,25 @@ const Face$json = {
|
||||
'1': 'Face',
|
||||
'2': [
|
||||
{'1': 'id', '3': 1, '4': 1, '5': 9, '9': 0, '10': 'id', '17': true},
|
||||
{'1': 'detection', '3': 2, '4': 1, '5': 11, '6': '.ente.ml.Detection', '9': 1, '10': 'detection', '17': true},
|
||||
{'1': 'confidence', '3': 3, '4': 1, '5': 2, '9': 2, '10': 'confidence', '17': true},
|
||||
{
|
||||
'1': 'detection',
|
||||
'3': 2,
|
||||
'4': 1,
|
||||
'5': 11,
|
||||
'6': '.ente.ml.Detection',
|
||||
'9': 1,
|
||||
'10': 'detection',
|
||||
'17': true
|
||||
},
|
||||
{
|
||||
'1': 'confidence',
|
||||
'3': 3,
|
||||
'4': 1,
|
||||
'5': 2,
|
||||
'9': 2,
|
||||
'10': 'confidence',
|
||||
'17': true
|
||||
},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_id'},
|
||||
@@ -52,4 +87,3 @@ final $typed_data.Uint8List faceDescriptor = $convert.base64Decode(
|
||||
'CgRGYWNlEhMKAmlkGAEgASgJSABSAmlkiAEBEjUKCWRldGVjdGlvbhgCIAEoCzISLmVudGUubW'
|
||||
'wuRGV0ZWN0aW9uSAFSCWRldGVjdGlvbogBARIjCgpjb25maWRlbmNlGAMgASgCSAJSCmNvbmZp'
|
||||
'ZGVuY2WIAQFCBQoDX2lkQgwKCl9kZXRlY3Rpb25CDQoLX2NvbmZpZGVuY2U=');
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'face.pb.dart';
|
||||
|
||||
|
||||
@@ -31,25 +31,30 @@ class FileML extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
FileML._() : super();
|
||||
factory FileML.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory FileML.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory FileML.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory FileML.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileML', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'FileML',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
|
||||
createEmptyInstance: create)
|
||||
..aInt64(1, _omitFieldNames ? '' : 'id')
|
||||
..p<$core.double>(2, _omitFieldNames ? '' : 'clip', $pb.PbFieldType.KD)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileML clone() => FileML()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileML copyWith(void Function(FileML) updates) => super.copyWith((message) => updates(message as FileML)) as FileML;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileML copyWith(void Function(FileML) updates) =>
|
||||
super.copyWith((message) => updates(message as FileML)) as FileML;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -58,13 +63,17 @@ class FileML extends $pb.GeneratedMessage {
|
||||
FileML createEmptyInstance() => create();
|
||||
static $pb.PbList<FileML> createRepeated() => $pb.PbList<FileML>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static FileML getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileML>(create);
|
||||
static FileML getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileML>(create);
|
||||
static FileML? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$fixnum.Int64 get id => $_getI64(0);
|
||||
@$pb.TagNumber(1)
|
||||
set id($fixnum.Int64 v) { $_setInt64(0, v); }
|
||||
set id($fixnum.Int64 v) {
|
||||
$_setInt64(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasId() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
@@ -101,28 +110,34 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
return $result;
|
||||
}
|
||||
FileFaces._() : super();
|
||||
factory FileFaces.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory FileFaces.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
factory FileFaces.fromBuffer($core.List<$core.int> i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory FileFaces.fromJson($core.String i,
|
||||
[$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'FileFaces', package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'), createEmptyInstance: create)
|
||||
..pc<$2.Face>(1, _omitFieldNames ? '' : 'faces', $pb.PbFieldType.PM, subBuilder: $2.Face.create)
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
_omitMessageNames ? '' : 'FileFaces',
|
||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'ente.ml'),
|
||||
createEmptyInstance: create)
|
||||
..pc<$2.Face>(1, _omitFieldNames ? '' : 'faces', $pb.PbFieldType.PM,
|
||||
subBuilder: $2.Face.create)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'height', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'width', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(4, _omitFieldNames ? '' : 'version', $pb.PbFieldType.O3)
|
||||
..aOS(5, _omitFieldNames ? '' : 'error')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileFaces clone() => FileFaces()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileFaces copyWith(void Function(FileFaces) updates) => super.copyWith((message) => updates(message as FileFaces)) as FileFaces;
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
FileFaces copyWith(void Function(FileFaces) updates) =>
|
||||
super.copyWith((message) => updates(message as FileFaces)) as FileFaces;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@@ -131,7 +146,8 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
FileFaces createEmptyInstance() => create();
|
||||
static $pb.PbList<FileFaces> createRepeated() => $pb.PbList<FileFaces>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static FileFaces getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileFaces>(create);
|
||||
static FileFaces getDefault() =>
|
||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<FileFaces>(create);
|
||||
static FileFaces? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
@@ -140,7 +156,10 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get height => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set height($core.int v) { $_setSignedInt32(1, v); }
|
||||
set height($core.int v) {
|
||||
$_setSignedInt32(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasHeight() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
@@ -149,7 +168,10 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get width => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set width($core.int v) { $_setSignedInt32(2, v); }
|
||||
set width($core.int v) {
|
||||
$_setSignedInt32(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasWidth() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
@@ -158,7 +180,10 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(4)
|
||||
$core.int get version => $_getIZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set version($core.int v) { $_setSignedInt32(3, v); }
|
||||
set version($core.int v) {
|
||||
$_setSignedInt32(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasVersion() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
@@ -167,13 +192,16 @@ class FileFaces extends $pb.GeneratedMessage {
|
||||
@$pb.TagNumber(5)
|
||||
$core.String get error => $_getSZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set error($core.String v) { $_setString(4, v); }
|
||||
set error($core.String v) {
|
||||
$_setString(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasError() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearError() => clearField(5);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
const _omitMessageNames =
|
||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
||||
|
||||
@@ -8,4 +8,3 @@
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
|
||||
@@ -34,10 +34,25 @@ final $typed_data.Uint8List fileMLDescriptor = $convert.base64Decode(
|
||||
const FileFaces$json = {
|
||||
'1': 'FileFaces',
|
||||
'2': [
|
||||
{'1': 'faces', '3': 1, '4': 3, '5': 11, '6': '.ente.ml.Face', '10': 'faces'},
|
||||
{
|
||||
'1': 'faces',
|
||||
'3': 1,
|
||||
'4': 3,
|
||||
'5': 11,
|
||||
'6': '.ente.ml.Face',
|
||||
'10': 'faces'
|
||||
},
|
||||
{'1': 'height', '3': 2, '4': 1, '5': 5, '9': 0, '10': 'height', '17': true},
|
||||
{'1': 'width', '3': 3, '4': 1, '5': 5, '9': 1, '10': 'width', '17': true},
|
||||
{'1': 'version', '3': 4, '4': 1, '5': 5, '9': 2, '10': 'version', '17': true},
|
||||
{
|
||||
'1': 'version',
|
||||
'3': 4,
|
||||
'4': 1,
|
||||
'5': 5,
|
||||
'9': 2,
|
||||
'10': 'version',
|
||||
'17': true
|
||||
},
|
||||
{'1': 'error', '3': 5, '4': 1, '5': 9, '9': 3, '10': 'error', '17': true},
|
||||
],
|
||||
'8': [
|
||||
@@ -54,4 +69,3 @@ final $typed_data.Uint8List fileFacesDescriptor = $convert.base64Decode(
|
||||
'dodBgCIAEoBUgAUgZoZWlnaHSIAQESGQoFd2lkdGgYAyABKAVIAVIFd2lkdGiIAQESHQoHdmVy'
|
||||
'c2lvbhgEIAEoBUgCUgd2ZXJzaW9uiAEBEhkKBWVycm9yGAUgASgJSANSBWVycm9yiAEBQgkKB1'
|
||||
'9oZWlnaHRCCAoGX3dpZHRoQgoKCF92ZXJzaW9uQggKBl9lcnJvcg==');
|
||||
|
||||
|
||||
@@ -11,4 +11,3 @@
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
export 'fileml.pb.dart';
|
||||
|
||||
|
||||
@@ -1735,15 +1735,23 @@
|
||||
"peopleWidgetDesc": "حدد الأشخاص الذين ترغب في ظهورهم على شاشتك الرئيسية.",
|
||||
"albumsWidgetDesc": "حدد الألبومات التي تريد ظهورها على شاشتك الرئيسية.",
|
||||
"memoriesWidgetDesc": "اختر نوع الذكريات التي ترغب في رؤيتها على شاشتك الرئيسية.",
|
||||
"smartMemories": "ذكريات ذكية",
|
||||
"pastYearsMemories": "ذكريات السنوات الماضية",
|
||||
"deleteMultipleAlbumDialog": "هل تريد أيضًا حذف الصور (والمقاطع) الموجودة في هذه الألبومات {count} من <bold>كافة</bold> الألبومات الأخرى التي تشترك فيها؟",
|
||||
"addParticipants": "إضافة مشاركين",
|
||||
"selectedAlbums": "{count} تم تحديد",
|
||||
"actionNotSupportedOnFavouritesAlbum": "الإجراء غير مدعوم في ألبوم المفضلة",
|
||||
"onThisDayMemories": "ذكريات هذا اليوم",
|
||||
"onThisDay": "في هذا اليوم",
|
||||
"lookBackOnYourMemories": "استرجع ذكرياتك 🌄",
|
||||
"newPhotosEmoji": " جديد 📸",
|
||||
"sorryWeHadToPauseYourBackups": "عذراً، لقد اضطررنا إلى إيقاف النسخ الاحتياطي الخاصة بك",
|
||||
"clickToInstallOurBestVersionYet": "انقر لتثبيت أفضل إصدار لنا حتى الآن",
|
||||
"onThisDayNotificationExplanation": "تلقي تذكيرات حول ذكريات مثل اليوم في السنوات السابقة.",
|
||||
"addMemoriesWidgetPrompt": "أضف أداة ذكريات إلى شاشتك الرئيسية، ثم عد إلى هنا لتخصيصها.",
|
||||
"addAlbumWidgetPrompt": "أضف أداة ألبوم إلى شاشتك الرئيسية، ثم عد إلى هنا لتخصيصها.",
|
||||
"addPeopleWidgetPrompt": "أضف عنصر واجهة الأشخاص إلى شاشتك الرئيسية ثم عد إلى هنا لتخصيصه.",
|
||||
"birthdayNotifications": "إشعارات عيد الميلاد",
|
||||
"receiveRemindersOnBirthdays": "استلم تذكيرات عندما يحين عيد ميلاد أحدهم. النقر على الإشعار سينقلك إلى صور الشخص المحتفل بعيد ميلاده.",
|
||||
"happyBirthday": "عيد ميلاد سعيد! 🥳",
|
||||
"birthdays": "أعياد الميلاد",
|
||||
@@ -1758,14 +1766,59 @@
|
||||
"ignore": "تجاهل",
|
||||
"merge": "دمج",
|
||||
"reset": "إعادة تعيين",
|
||||
"areYouSureYouWantToIgnoreThisPerson": "هل أنت متأكد من أنك تريد تجاهل هذا الشخص؟",
|
||||
"areYouSureYouWantToIgnoreThesePersons": "هل أنت متأكد أنك تريد تجاهل هؤلاء الأشخاص؟",
|
||||
"thePersonGroupsWillNotBeDisplayed": "لن تظهر مجموعات الأشخاص في قسم الأشخاص بعد الآن. ستظل الصور دون تغيير.",
|
||||
"thePersonWillNotBeDisplayed": "لن يتم عرض هذا الشخص في قسم الأشخاص بعد الآن. الصور ستبقى كما هي دون تغيير.",
|
||||
"areYouSureYouWantToMergeThem": "هل أنت متأكد من رغبتك في دمجهم؟",
|
||||
"allUnnamedGroupsWillBeMergedIntoTheSelectedPerson": "سيتم دمج جميع المجموعات غير المسماة مع الشخص المحدد. يمكن التراجع عن هذا الإجراء لاحقًا من خلال نظرة عامة على سجل الاقتراحات التابع لهذا الشخص.",
|
||||
"yesIgnore": "نعم، تجاهل",
|
||||
"same": "نفس",
|
||||
"different": "مختلف",
|
||||
"sameperson": "نفس الشخص؟",
|
||||
"cLTitle1": "محرر الصور المتقدم",
|
||||
"cLDesc1": "نحن بصدد إطلاق محرر صور جديد ومتقدم يضيف المزيد من إطارات الاقتصاص، والإعدادات المسبقة للفلاتر من أجل تعديلات سريعة، وخيارات الضبط الدقيق التي تشمل التشبع، والتباين، والسطوع، ودرجة الحرارة، وغير ذلك الكثير. يتضمن المحرر الجديد أيضا القدرة على الرسم على صورك وإضافة الرموز التعبيرية كملصقات.",
|
||||
"cLTitle2": "ألبومات ذكية",
|
||||
"cLTitle3": "معرض محسن",
|
||||
"cLTitle4": "تمرير أسرع",
|
||||
"thisWeek": "هذا الأسبوع",
|
||||
"lastWeek": "الأسبوع الماضي",
|
||||
"thisMonth": "هذا الشهر",
|
||||
"thisYear": "هذه السنة",
|
||||
"day": "اليوم"
|
||||
"groupBy": "تجميع حسب",
|
||||
"faceThumbnailGenerationFailed": "تعذر إنشاء الصور المصغرة للوجه",
|
||||
"fileAnalysisFailed": "تعذر تحليل الملف",
|
||||
"editAutoAddPeople": "تحرير إضافة الأشخاص التلقائية",
|
||||
"autoAddPeople": "إضافة الأشخاص التلقائية",
|
||||
"autoAddToAlbum": "إضافة تلقائية إلى الألبوم",
|
||||
"addingPhotos": "جاري إضافة الصور",
|
||||
"gettingReady": "جاري الإعداد",
|
||||
"addSomePhotosDesc2": "وجوه مألوفة",
|
||||
"addSomePhotosDesc3": "\nللبدء بـ",
|
||||
"ignorePerson": "تجاهل الشخص",
|
||||
"analysis": "تحليل",
|
||||
"doesGroupContainMultiplePeople": "هل هذه المجموعة تحتوي على أشخاص متعددين؟",
|
||||
"automaticallyAnalyzeAndSplitGrouping": "سنقوم تلقائيا بتحليل المجموعة لتحديد ما إذا كان هناك عدة أشخاص، وفصلهم مرة أخرى. قد يستغرق هذا بضع ثوان.",
|
||||
"layout": "التخطيط",
|
||||
"day": "اليوم",
|
||||
"peopleAutoAddDesc": "حدد الأشخاص الذين تريد إضافتهم تلقائيا إلى الألبوم",
|
||||
"undo": "تراجع",
|
||||
"redo": "إعادة",
|
||||
"filter": "فلتر",
|
||||
"adjust": "تعديل",
|
||||
"draw": "رسم",
|
||||
"sticker": "ملصق",
|
||||
"brushColor": "لون الفرشاة",
|
||||
"font": "الخط",
|
||||
"background": "خلفية",
|
||||
"align": "محاذاة",
|
||||
"addedToAlbums": "{count, plural, =1{تمت الإضافة إلى ألبوم واحد بنجاح} other{تم الإضافة إلى {count} ألبومات بنجاح}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1776,6 +1776,14 @@
|
||||
"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",
|
||||
@@ -1800,5 +1808,24 @@
|
||||
"automaticallyAnalyzeAndSplitGrouping": "Wir analysieren die Gruppierung automatisch, um festzustellen, ob mehrere Personen vorhanden sind, und trennen sie erneut. Dies kann ein paar Sekunden dauern.",
|
||||
"layout": "Layout",
|
||||
"day": "Tag",
|
||||
"peopleAutoAddDesc": "Wähle die Personen, die du automatisch zum Album hinzufügen möchtest"
|
||||
"peopleAutoAddDesc": "Wähle die Personen, die du automatisch zum Album hinzufügen möchtest",
|
||||
"undo": "Rückgängig",
|
||||
"redo": "Wiederherstellen",
|
||||
"filter": "Filter",
|
||||
"adjust": "Anpassen",
|
||||
"draw": "Zeichnen",
|
||||
"sticker": "Sticker",
|
||||
"brushColor": "Pinselfarbe",
|
||||
"font": "Schriftart",
|
||||
"background": "Hintergrund",
|
||||
"align": "Ausrichten",
|
||||
"addedToAlbums": "{count, plural, =1{Erfolgreich zu einem Album hinzugefügt} other{Erfolgreich zu {count} Alben hinzugefügt}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1758,7 +1758,7 @@
|
||||
"wishThemAHappyBirthday": "Souhaitez à {name} un joyeux anniversaire ! 🎉",
|
||||
"areYouSureRemoveThisFaceFromPerson": "Êtes-vous sûr de vouloir retirer ce visage de cette personne ?",
|
||||
"otherDetectedFaces": "Autres visages détectés",
|
||||
"areThey": "Vraiment",
|
||||
"areThey": "S'agit-il de ",
|
||||
"questionmark": "?",
|
||||
"saveAsAnotherPerson": "Enregistrer comme une autre personne",
|
||||
"showLessFaces": "Afficher moins de visages",
|
||||
@@ -1776,7 +1776,56 @@
|
||||
"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",
|
||||
"thisMonth": "Ce mois",
|
||||
"thisYear": "Cette année",
|
||||
"groupBy": "Grouper par",
|
||||
"faceThumbnailGenerationFailed": "Impossible de créer des miniatures de visage",
|
||||
"fileAnalysisFailed": "Impossible d'analyser le fichier"
|
||||
"fileAnalysisFailed": "Impossible d'analyser le fichier",
|
||||
"editAutoAddPeople": "Modifier les personnes auto-ajoutées",
|
||||
"autoAddPeople": "Ajouter automatiquement des personnes",
|
||||
"autoAddToAlbum": "Ajouter automatiquement à l'album",
|
||||
"shouldRemoveFilesSmartAlbumsDesc": "Les fichiers liés à la personne qui ont été précédemment sélectionnés dans les albums intelligents doivent-ils être supprimés ?",
|
||||
"addingPhotos": "Ajout des photos",
|
||||
"gettingReady": "Préparation",
|
||||
"addSomePhotosDesc1": "Ajoutez quelques photos ou choisissez des ",
|
||||
"addSomePhotosDesc2": "visages familiers",
|
||||
"addSomePhotosDesc3": "\npour commencer",
|
||||
"ignorePerson": "Ignorer la personne",
|
||||
"mixedGrouping": "Plusieurs personnes?",
|
||||
"analysis": "Analyse",
|
||||
"doesGroupContainMultiplePeople": "Ce groupement contient-il plusieurs personnes ?",
|
||||
"automaticallyAnalyzeAndSplitGrouping": "Nous analyserons automatiquement le regroupement pour déterminer s'il y a plusieurs personnes présentes et nous les séparerons à nouveau si tel est le cas. Cela peut prendre quelques secondes.",
|
||||
"layout": "Disposition",
|
||||
"day": "Jour",
|
||||
"peopleAutoAddDesc": "Sélectionnez les personnes que vous souhaitez ajouter automatiquement à l'album",
|
||||
"undo": "Annuler",
|
||||
"redo": "Rétablir",
|
||||
"filter": "Filtres",
|
||||
"adjust": "Ajuster",
|
||||
"draw": "Dessiner",
|
||||
"sticker": "Autocollant",
|
||||
"brushColor": "Couleur du pinceau",
|
||||
"font": "Police",
|
||||
"background": "Arrière-plan",
|
||||
"align": "Aligner",
|
||||
"addedToAlbums": "{count, plural, one {}=1{Ajouté avec succès à 1 album} other{Ajouté avec succès à {count} albums}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1776,6 +1776,14 @@
|
||||
"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ę",
|
||||
@@ -1784,8 +1792,31 @@
|
||||
"groupBy": "Grupuoti pagal",
|
||||
"faceThumbnailGenerationFailed": "Nepavyksta sugeneruoti veido miniatiūrų.",
|
||||
"fileAnalysisFailed": "Nepavyksta išanalizuoti failo.",
|
||||
"editAutoAddPeople": "Redaguoti automatiškai įtrauktus asmenis",
|
||||
"autoAddPeople": "Automatiškai įtraukti asmenis",
|
||||
"autoAddToAlbum": "Automatiškai įtraukti į albumą",
|
||||
"shouldRemoveFilesSmartAlbumsDesc": "Ar reikia pašalinti failus, susijusius su asmeniu, kuris anksčiau buvo atrinktas išmaniuosiuose albumuose?",
|
||||
"addingPhotos": "Įtraukiamos nuotraukos",
|
||||
"gettingReady": "Pasirengiama",
|
||||
"addSomePhotosDesc1": "Įtraukite šiek tiek nuotraukų arba pasirinkite ",
|
||||
"addSomePhotosDesc2": "pažįstamus veidus",
|
||||
"addSomePhotosDesc3": "\niš pradžių.",
|
||||
"ignorePerson": "Ignoruoti asmenį",
|
||||
"mixedGrouping": "Sumaišytas grupavimas?",
|
||||
"analysis": "Analizė",
|
||||
"doesGroupContainMultiplePeople": "Ar šiame grupavime yra keli asmenys?",
|
||||
"automaticallyAnalyzeAndSplitGrouping": "Mes automatiškai išanalizuosime grupavimą, kad nustatytume, ar yra keli asmenys, ir vėl juos atskirsime. Tai gali užtrukti keletą sekundžių.",
|
||||
"layout": "Išdėstymas",
|
||||
"day": "Diena"
|
||||
"day": "Diena",
|
||||
"peopleAutoAddDesc": "Pasirinkite asmenis, kuriuos norite automatiškai įtraukti į albumą.",
|
||||
"undo": "Anuliuoti",
|
||||
"redo": "Grąžinti",
|
||||
"filter": "Filtruoti",
|
||||
"adjust": "Priderinti",
|
||||
"draw": "Piešti",
|
||||
"sticker": "Lipdukas",
|
||||
"brushColor": "Teptuko spalva",
|
||||
"font": "Šriftas",
|
||||
"background": "Fonas",
|
||||
"align": "Lygiuoti"
|
||||
}
|
||||
313
mobile/apps/photos/lib/l10n/intl_nn.arb
Normal file
313
mobile/apps/photos/lib/l10n/intl_nn.arb
Normal file
@@ -0,0 +1,313 @@
|
||||
{
|
||||
"@@locale ": "en",
|
||||
"enterYourEmailAddress": "Skriv inn e-postadressa di",
|
||||
"enterYourNewEmailAddress": "Skriv inn den nye e-postadressa di",
|
||||
"accountWelcomeBack": "Velkommen tilbake!",
|
||||
"emailAlreadyRegistered": "E-postadressa er registrert frå før.",
|
||||
"emailNotRegistered": "E-postadressa er ikkje registrert.",
|
||||
"email": "E-post",
|
||||
"cancel": "Avbryt",
|
||||
"verify": "Stadfest",
|
||||
"invalidEmailAddress": "Ugyldig e-postadresse",
|
||||
"enterValidEmail": "Skriv inn ei gyldig e-postadresse.",
|
||||
"deleteAccount": "Slett konto",
|
||||
"askDeleteReason": "Kva er hovudgrunnen til at du vil sletta kontoen din?",
|
||||
"feedback": "Tilbakemelding",
|
||||
"confirmDeletePrompt": "Ja, eg vil sletta denne kontoen og alle tilhøyrande data frå alle appar permanent.",
|
||||
"confirmAccountDeletion": "Stadfest sletting av konto",
|
||||
"deleteAccountPermanentlyButton": "Slett konto permanent",
|
||||
"yourAccountHasBeenDeleted": "Kontoen er sletta",
|
||||
"sendEmail": "Send e-post",
|
||||
"entePhotosPerm": "Ente <i>treng løyve til</i> å kunna ta vare på fotoa dine",
|
||||
"ok": "OK",
|
||||
"createAccount": "Opprett konto",
|
||||
"createNewAccount": "Opprett konto",
|
||||
"password": "Passord",
|
||||
"confirmPassword": "Stadfest passord",
|
||||
"activeSessions": "Aktive økter",
|
||||
"oops": "Uff då",
|
||||
"somethingWentWrongPleaseTryAgain": "Det oppstod ein feil. Prøv på nytt.",
|
||||
"thisWillLogYouOutOfThisDevice": "Dette loggar deg ut frå denne eininga.",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "Dette loggar deg ut frå dei følgjande einingane:",
|
||||
"terminateSession": "Avslutta økt?",
|
||||
"terminate": "Avslutt",
|
||||
"thisDevice": "Denne eininga",
|
||||
"recoverButton": "Gjenopprett",
|
||||
"recoverySuccessful": "Gjenoppretting var vellukka.",
|
||||
"decrypting": "Dekrypterer …",
|
||||
"incorrectRecoveryKeyTitle": "Gjenopprettingsnøkkelen er feil",
|
||||
"incorrectRecoveryKeyBody": "Gjenopprettingsnøkkelen som du skreiv inn er feil",
|
||||
"forgotPassword": "Gløymt passordet",
|
||||
"enterYourRecoveryKey": "Skriv inn gjenopprettingsnøkkelen",
|
||||
"noRecoveryKey": "Ingen gjenopprettingsnøkkel?",
|
||||
"sorry": "Orsak",
|
||||
"verifyEmail": "Stadfest e-postadresse",
|
||||
"resendEmail": "Send e-posten på nytt",
|
||||
"weHaveSendEmailTo": "Vi har sendt ein e-post til <green> {email} </green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"description": "The email address of the user",
|
||||
"type": "String",
|
||||
"example": "example@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"setPasswordTitle": "Vel passord",
|
||||
"changePasswordTitle": "Endra passord",
|
||||
"resetPasswordTitle": "Tilbakestill passord",
|
||||
"encryptionKeys": "Krypteringsnøklar",
|
||||
"passwordWarning": "Vi lagrar ikkje dette passordet, så dersom du gløymer det <underline>kan vi ikkje dekryptera dataa dine</underline>",
|
||||
"enterPasswordToEncrypt": "Skriv inn eit passord me kan kryptera dataa dine med",
|
||||
"enterNewPasswordToEncrypt": "Skriv inn eit nytt passord me kan kryptera dataa dine med",
|
||||
"weakStrength": "Svakt",
|
||||
"strongStrength": "Sterkt",
|
||||
"moderateStrength": "Middels sterkt",
|
||||
"passwordStrength": "Passordstyrke: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
"placeholders": {
|
||||
"passwordStrengthValue": {
|
||||
"description": "The strength of the password as a string",
|
||||
"type": "String",
|
||||
"example": "Weak or Moderate or Strong"
|
||||
}
|
||||
},
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"passwordChangedSuccessfully": "Passordet er endra",
|
||||
"generatingEncryptionKeys": "Lagar krypteringsnøklar …",
|
||||
"pleaseWait": "Vent litt …",
|
||||
"continueLabel": "Fortsett",
|
||||
"insecureDevice": "Usikker eining",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Klarte ikkje laga tryggleiksnøklar på denne eininga.\n\nBruk ei anna eining for å registrera deg.",
|
||||
"howItWorks": "Slik fungerer det",
|
||||
"encryption": "Kryptering",
|
||||
"ackPasswordLostWarning": "Eg forstår at dersom eg gløymer passordet mitt kan alle dataa mine gå tapt, fordi dei er <underline>ende-til-ende-krypterte</underline>.",
|
||||
"privacyPolicyTitle": "Personvernerklæring",
|
||||
"termsOfServicesTitle": "Vilkår",
|
||||
"signUpTerms": "Eg godtek <u-terms>tenestevilkåra</u-terms> og <u-policy>personvernerklæringa</u-policy>",
|
||||
"logInLabel": "Logg på",
|
||||
"changeEmail": "Endra e-postadresse",
|
||||
"enterYourPassword": "Skriv inn passordet ditt",
|
||||
"welcomeBack": "Velkommen tilbake!",
|
||||
"incorrectPasswordTitle": "Ugyldig passord",
|
||||
"pleaseTryAgain": "Prøv på nytt",
|
||||
"recreatePasswordTitle": "Opprett passord på nytt",
|
||||
"useRecoveryKey": "Bruk gjenopprettingsnøkkel",
|
||||
"recreatePasswordBody": "Den gjeldande eininga er ikkje kraftig nok til å kunna stadfesta passordet ditt, men me kan laga det på nytt ved å bruka ein metode som vil fungere på alle einingar.\n\nLogg på ved å bruka gjenopprettingsnøkkelen din og lag passordet på nytt (du kan gjenbruka passordet viss du vil).",
|
||||
"verifyPassword": "Stadfest passord",
|
||||
"recoveryKey": "Gjenopprettingsnøkkel",
|
||||
"recoveryKeyOnForgotPassword": "Viss du gløymer passordet, er det berre ved å bruka denne nøkkelen at du kan gjenoppretta dataa dine.",
|
||||
"recoveryKeySaveDescription": "Me tek ikkje vare på denne nøkkelen. Denne nøkkelen på 24 ord må du oppbevara på ein trygg stad.",
|
||||
"doThisLater": "Gjer dette seinare",
|
||||
"saveKey": "Lagra nøkkel",
|
||||
"recoveryKeyCopiedToClipboard": "Gjenopprettingsnøkkel kopiert til utklippstavla",
|
||||
"recoverAccount": "Gjenopprett konto",
|
||||
"recover": "Gjenopprett",
|
||||
"dropSupportEmail": "Send ein e-post til {supportEmail} frå e-postadressa du registrerte deg med",
|
||||
"@dropSupportEmail": {
|
||||
"placeholders": {
|
||||
"supportEmail": {
|
||||
"description": "The support email address",
|
||||
"type": "String",
|
||||
"example": "support@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"twofactorSetup": "Oppsett av tofaktor",
|
||||
"enterCode": "Skriv inn kode",
|
||||
"scanCode": "Skann kode",
|
||||
"codeCopiedToClipboard": "Kode kopiert til utklippstavla",
|
||||
"copypasteThisCodentoYourAuthenticatorApp": "Kopier og lim inn koden\ni autentikator-appen",
|
||||
"tapToCopy": "trykk for å kopiera",
|
||||
"scanThisBarcodeWithnyourAuthenticatorApp": "Skann denne strekkoden med\nautentikator-appen",
|
||||
"enterThe6digitCodeFromnyourAuthenticatorApp": "Skriv inn den seks-sifra koden\nfrå autentikator-appen",
|
||||
"confirm": "Stadfest",
|
||||
"setupComplete": "Oppsettet er fullført",
|
||||
"saveYourRecoveryKeyIfYouHaventAlready": "Lagra gjenopprettingsnøkkelen viss du ikkje alt har gjort det",
|
||||
"twofactorAuthenticationPageTitle": "Tofaktorautentisering",
|
||||
"verifyingRecoveryKey": "Stadfestar gjenopprettingsnøkkel …",
|
||||
"recoveryKeyVerified": "Gjenopprettingsnøkkelen er stadfesta",
|
||||
"recoveryKeySuccessBody": "Flott! Gjenopprettingsnøkkelen er gyldig. Takk for at du stadfesta han.\n\nHugs å oppbevara gjenopprettingsnøkkelen på ein trygg stad.",
|
||||
"invalidRecoveryKey": "Gjenopprettingsnøkkelen er ikkje gyldig. Pass på at han inneheld 24 ord og er stava riktig.\n\nViss du skreiv inn ein eldre gjenopprettingskode, pass på at han inneheld 64 teikn.",
|
||||
"invalidKey": "Ugyldig nøkkel",
|
||||
"tryAgain": "Prøv på nytt",
|
||||
"viewRecoveryKey": "Vis gjenopprettingsnøkkel",
|
||||
"confirmRecoveryKey": "Stadfest gjenopprettingsnøkkel",
|
||||
"recoveryKeyVerifyReason": "Viss du gløymer passordet, er det berre ved å bruka gjenopprettingsnøkkelen at du kan gjenoppretta fotoa dine. Vel «Konto» frå «Innstillingar»-menyen for å finna gjenopprettingsnøkkelen.\n\nSkriv inn gjenopprettingsnøkkelen her for å stadfesta at du har lagra han på riktig måte.",
|
||||
"confirmYourRecoveryKey": "Stadfest gjenopprettingsnøkkelen din",
|
||||
"addANewEmail": "Legg til ei ny e-postadresse",
|
||||
"enterEmail": "Skriv inn e-postadresse",
|
||||
"albumOwner": "Eigar",
|
||||
"@albumOwner": {
|
||||
"description": "Role of the album owner"
|
||||
},
|
||||
"you": "Deg",
|
||||
"enterPassword": "Skriv inn passord",
|
||||
"removeLink": "Fjern lenkje",
|
||||
"manageLink": "Handsam lenkje",
|
||||
"done": "Ferdig",
|
||||
"details": "Detaljar",
|
||||
"faq": "Spørsmål og svar",
|
||||
"help": "Hjelp",
|
||||
"oopsSomethingWentWrong": "Uff då. Noko gjekk gale.",
|
||||
"addingToFavorites": "Legger til i favorittar …",
|
||||
"removingFromFavorites": "Fjernar frå favorittar …",
|
||||
"sorryCouldNotAddToFavorites": "Klarte ikkje leggja til i favorittar.",
|
||||
"sorryCouldNotRemoveFromFavorites": "Klarte ikkje fjerna frå favorittar.",
|
||||
"archive": "Arkiv",
|
||||
"createAlbumActionHint": "Trykk lenge for å velja foto, og trykk på pluss-ikonet for å oppretta eit album",
|
||||
"importing": "Importerer …",
|
||||
"failedToLoadAlbums": "Feil ved lasting av album",
|
||||
"hidden": "Gøymde",
|
||||
"authToViewYourHiddenFiles": "Autentiser deg for å visa gøymde filer",
|
||||
"authToViewTrashedFiles": "Autentiser deg for å visa filer i papirkorga",
|
||||
"trash": "Papirkorg",
|
||||
"uncategorized": "Ikkje-kategoriserte",
|
||||
"videoSmallCase": "video",
|
||||
"photoSmallCase": "foto",
|
||||
"deleteFromBoth": "Slett frå begge",
|
||||
"newAlbum": "Nytt album",
|
||||
"albums": "Albums",
|
||||
"memoryCount": "{count, plural, =0{ingen minne} one{{formattedCount} minne} other{{formattedCount} minne}}",
|
||||
"@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} valde",
|
||||
"@selectedPhotos": {
|
||||
"description": "Display the number of selected photos",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "5",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"selectedPhotosWithYours": "{count} valde ({yourCount} av desse er dine)",
|
||||
"@selectedPhotosWithYours": {
|
||||
"description": "Display the number of selected photos, including the number of selected photos owned by the user",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "12",
|
||||
"type": "int"
|
||||
},
|
||||
"yourCount": {
|
||||
"example": "2",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"advancedSettings": "Avansert",
|
||||
"@advancedSettings": {
|
||||
"description": "The text to display in the advanced settings section"
|
||||
},
|
||||
"discover_babies": "Babyar",
|
||||
"discover_pets": "Kjæledyr",
|
||||
"discover_selfies": "Selfiar",
|
||||
"discover_food": "Mat",
|
||||
"discover_celebrations": "Feiringar",
|
||||
"discover_sunset": "Solnedgang",
|
||||
"discover_hills": "Bakkar",
|
||||
"loadingModel": "Lastar ned modellar …",
|
||||
"waitingForWifi": "Ventar på Wi-Fi …",
|
||||
"status": "Status",
|
||||
"indexedItems": "Indekserte element",
|
||||
"selectedFoldersWillBeEncryptedAndBackedUp": "Dei valde mappene vert krypterte og reservekopierte",
|
||||
"unselectAll": "Fjern all merking",
|
||||
"selectAll": "Merk alle",
|
||||
"skip": "Hopp over",
|
||||
"backupSettings": "Innstillingar for reservekopiering",
|
||||
"backupStatus": "Status for reservekopiering",
|
||||
"backupStatusDescription": "Reservekopierte element vert viste her",
|
||||
"backupOverMobileData": "Ta reservekopi via mobildata",
|
||||
"backupVideos": "Ta reservekopi av videoar",
|
||||
"youAreOnTheLatestVersion": "Du har siste versjon",
|
||||
"account": "Konto",
|
||||
"manageSubscription": "Handsam abonnement",
|
||||
"authToChangeYourEmail": "Autentiser for å endra e-postadresse",
|
||||
"changePassword": "Endra passord",
|
||||
"authToChangeYourPassword": "Autentiser for å endra passord",
|
||||
"exportYourData": "Eksporter dataa dine",
|
||||
"logout": "Logg ut",
|
||||
"areYouSureYouWantToLogout": "Er du sikker på at du vil logga ut?",
|
||||
"yesLogout": "Ja, logg ut",
|
||||
"aNewVersionOfEnteIsAvailable": "Ein ny versjon av Ente er tilgjengeleg.",
|
||||
"update": "Oppdater",
|
||||
"installManually": "Installer manuelt",
|
||||
"criticalUpdateAvailable": "Kritisk oppdatering tilgjengeleg",
|
||||
"updateAvailable": "Oppdatering tilgjengeleg",
|
||||
"ignoreUpdate": "Ignorer",
|
||||
"downloading": "Lastar ned …",
|
||||
"cannotDeleteSharedFiles": "Kan ikkje sletta delte filer",
|
||||
"theDownloadCouldNotBeCompleted": "Klarte ikkje fullføra nedlastinga",
|
||||
"retry": "Prøv på nytt",
|
||||
"backup": "Reservekopiering",
|
||||
"freeUpDeviceSpace": "Frigjer plass på eininga",
|
||||
"freeUpDeviceSpaceDesc": "Frigjer plass på eininga ved å fjerna filer som allereie er reservekopierte.",
|
||||
"removeDuplicates": "Fjern duplikat",
|
||||
"removeDuplicatesDesc": "Sjå gjennom og fjern duplikate filer.",
|
||||
"viewLargeFiles": "Store filer",
|
||||
"viewLargeFilesDesc": "Vis filer som tek opp mest plass.",
|
||||
"youHaveSuccessfullyFreedUp": "Du har frigjort {storageSaved}!",
|
||||
"@youHaveSuccessfullyFreedUp": {
|
||||
"description": "The text to display when the user has successfully freed up storage",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"storageSaved": {
|
||||
"example": "1.2 GB",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"advanced": "Avansert",
|
||||
"general": "Generelt",
|
||||
"security": "Tryggleik",
|
||||
"faqs": "Spørsmål og svar",
|
||||
"subscription": "Abonnement",
|
||||
"startBackup": "Start reservekopiering",
|
||||
"privateBackups": "Private reservekopiar",
|
||||
"backupFailed": "Feil ved reservekopiering",
|
||||
"sorryBackupFailedDesc": "Klarte ikkje reservekopiera fila. Me prøver på nytt seinare.",
|
||||
"couldNotBackUpTryLater": "Klarte ikkje reservekopiera dataa dine.\nMe prøver på nytt seinare.",
|
||||
"renameAlbum": "Endra namn på album",
|
||||
"rename": "Endra namn",
|
||||
"noExifData": "Ingen Exif-data",
|
||||
"thisImageHasNoExifData": "Biletet har ingen exif-data",
|
||||
"exif": "Exif",
|
||||
"encryptingBackup": "Krypterer reservekopi …",
|
||||
"renameFile": "Endra namn på fil",
|
||||
"rotateLeft": "Roter til venstre",
|
||||
"flip": "Spegelvend",
|
||||
"rotateRight": "Roter til høgre",
|
||||
"saveCopy": "Lagra kopi",
|
||||
"fileInfoAddDescHint": "Legg til skildring …",
|
||||
"androidCancelButton": "Avbryt",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"crop": "Skjer av",
|
||||
"rotate": "Roter",
|
||||
"next": "Neste",
|
||||
"noFacesFound": "Fann ingen ansikt",
|
||||
"selectDate": "Vel dato",
|
||||
"areThey": "Er dette ",
|
||||
"filter": "Filter",
|
||||
"adjust": "Juster",
|
||||
"draw": "Klistremerke",
|
||||
"brushColor": "Penselfarge"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"@@locale ": "en",
|
||||
"enterYourEmailAddress": "Skriv inn e-postadressen din",
|
||||
"enterYourNewEmailAddress": "Skriv inn den nye e-postadressen din",
|
||||
"accountWelcomeBack": "Velkommen tilbake!",
|
||||
"emailAlreadyRegistered": "E-postadressen er allerede registrert.",
|
||||
"emailNotRegistered": "E-postadressen er ikke registrert.",
|
||||
@@ -721,6 +722,7 @@
|
||||
"type": "text"
|
||||
},
|
||||
"backupFailed": "Sikkerhetskopiering mislyktes",
|
||||
"sorryBackupFailedDesc": "Beklager, vi kunne ikke sikkerhetskopiere denne filen akkurat nå, vil vi prøve på nytt senere.",
|
||||
"couldNotBackUpTryLater": "Vi kunne ikke sikkerhetskopiere dine data.\nVi vil prøve på nytt senere.",
|
||||
"enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Ente kan bare kryptere og bevare filer hvis du gir tilgang til dem",
|
||||
"pleaseGrantPermissions": "Vennligst gi tillatelser",
|
||||
@@ -897,6 +899,7 @@
|
||||
"authToViewYourMemories": "Vennligst autentiser deg for å se minnene dine",
|
||||
"unlock": "Lås opp",
|
||||
"freeUpSpace": "Frigjør lagringsplass",
|
||||
"freeUpSpaceSaving": "{count, plural, =1 {Det kan slettes fra enheten for å frigi {formattedSize}} other {De kan slettes fra enheten for å frigjøre {formattedSize}}}",
|
||||
"filesBackedUpInAlbum": "{count, plural, one {1 fil} other {{formattedNumber} filer}} I dette albumet har blitt sikkerhetskopiert",
|
||||
"@filesBackedUpInAlbum": {
|
||||
"description": "Text to tell user how many files have been backed up in the album",
|
||||
@@ -927,6 +930,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@freeUpSpaceSaving": {
|
||||
"description": "Text to tell user how much space they can free up by deleting items from the device"
|
||||
},
|
||||
"freeUpAccessPostDelete": "Du kan fortsatt få tilgang til {count, plural, =1 {det} other {dem}} på Ente så lenge du har et aktivt abonnement",
|
||||
"@freeUpAccessPostDelete": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"freeUpAmount": "Frigjør {sizeInMBorGB}",
|
||||
"thisEmailIsAlreadyInUse": "Denne e-postadressen er allerede i bruk",
|
||||
"incorrectCode": "Feil kode",
|
||||
@@ -1016,6 +1031,7 @@
|
||||
"didYouKnow": "Visste du at?",
|
||||
"loadingMessage": "Laster bildene dine...",
|
||||
"loadMessage1": "Du kan dele abonnementet med familien din",
|
||||
"loadMessage2": "Vi har bevart over 200 millioner minner så langt",
|
||||
"loadMessage3": "Vi beholder 3 kopier av dine data, en i en underjordisk bunker",
|
||||
"loadMessage4": "Alle våre apper har åpen kildekode",
|
||||
"loadMessage5": "Vår kildekode og kryptografi har blitt revidert eksternt",
|
||||
@@ -1253,6 +1269,8 @@
|
||||
"description": "Subtitle to indicate that the user can find people quickly by name"
|
||||
},
|
||||
"findPeopleByName": "Finn folk raskt med navn",
|
||||
"addViewers": "{count, plural, =0 {Legg til seer} =1 {Legg til seer} other {Legg til seere}}",
|
||||
"addCollaborators": "{count, plural, =0 {Legg til samarbeidspartner} =1 {Legg til samarbeidspartner} other {Legg til samarbeidspartnere}}",
|
||||
"longPressAnEmailToVerifyEndToEndEncryption": "Trykk og hold på en e-post for å bekrefte ende-til-ende-kryptering.",
|
||||
"developerSettingsWarning": "Er du sikker på at du vil endre utviklerinnstillingene?",
|
||||
"developerSettings": "Utviklerinnstillinger",
|
||||
@@ -1264,6 +1282,8 @@
|
||||
"createCollaborativeLink": "Samarbeidslenke",
|
||||
"search": "Søk",
|
||||
"enterPersonName": "Angi personnavn",
|
||||
"editEmailAlreadyLinked": "Denne e-postadressen er allerede koblet til {name}.",
|
||||
"viewPersonToUnlink": "Vis {name} for å fjerne koblingen",
|
||||
"enterName": "Angi navn",
|
||||
"savePerson": "Lagre person",
|
||||
"editPerson": "Rediger person",
|
||||
@@ -1383,6 +1403,16 @@
|
||||
"enableMachineLearningBanner": "Aktiver maskinlæring for magisk søk og ansiktsgjenkjenning",
|
||||
"searchDiscoverEmptySection": "Bilder vil vises her når behandlingen og synkronisering er fullført",
|
||||
"searchPersonsEmptySection": "Folk vil vises her når behandling og synkronisering er fullført",
|
||||
"viewersSuccessfullyAdded": "{count, plural, =0 {La til 0 tilskuere} =1 {La til 1 tilskuer} other {La til {count} tilskuer}}",
|
||||
"@viewersSuccessfullyAdded": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
},
|
||||
"description": "Number of viewers that were successfully added to an album."
|
||||
},
|
||||
"collaboratorsSuccessfullyAdded": "{count, plural, =0 {La til 0 samarbeidspartner} =1 {La til 1 samarbeidspartner} other {Lagt til {count} samarbeidspartnere}}",
|
||||
"@collaboratorsSuccessfullyAdded": {
|
||||
"placeholders": {
|
||||
@@ -1458,6 +1488,15 @@
|
||||
},
|
||||
"currentlyRunning": "Kjører for øyeblikket",
|
||||
"ignored": "ignorert",
|
||||
"photosCount": "{count, plural, =0 {0 bilder} =1 {1 bilde} other {{count} bilder}}",
|
||||
"@photosCount": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"file": "Fil",
|
||||
"searchSectionsLengthMismatch": "Uoverensstemmelse i seksjonslengde: {snapshotLength} != {searchLength}",
|
||||
"@searchSectionsLengthMismatch": {
|
||||
@@ -1623,6 +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": "Strømbare videoer",
|
||||
"processingVideos": "Behandler videoer",
|
||||
"streamDetails": "Strømmedetaljer",
|
||||
"processing": "Behandler",
|
||||
@@ -1688,5 +1728,13 @@
|
||||
"moon": "I månelyset",
|
||||
"onTheRoad": "På veien igjen",
|
||||
"food": "Kulinær glede",
|
||||
"pets": "Pelsvenner"
|
||||
"pets": "Pelsvenner",
|
||||
"curatedMemories": "Kuraterte minner",
|
||||
"widgets": "Moduler",
|
||||
"memories": "Minner",
|
||||
"peopleWidgetDesc": "Velg folkene du ønsker å se på din hjemskjerm.",
|
||||
"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"
|
||||
}
|
||||
@@ -372,6 +372,21 @@
|
||||
"deleteFromBoth": "Usuń z obu",
|
||||
"newAlbum": "Nowy album",
|
||||
"albums": "Albumy",
|
||||
"memoryCount": "{count, plural, =0{brak wspomnień} one{{formattedCount} wspomnienie} few{{formattedCount} wspomnienia} many{{formattedCount} wspomnień} other{{formattedCount} wspomnień}}",
|
||||
"@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": "Wybrano {count}",
|
||||
"@selectedPhotos": {
|
||||
"description": "Display the number of selected photos",
|
||||
@@ -779,6 +794,14 @@
|
||||
"share": "Udostępnij",
|
||||
"unhideToAlbum": "Odkryj do albumu",
|
||||
"restoreToAlbum": "Przywróć do albumu",
|
||||
"moveItem": "{count, plural, =1 {Przenieś element} few {Przenieś elementy} many {Przenieś elementów} other {Przenieś elementów}}",
|
||||
"@moveItem": {
|
||||
"description": "Page title while moving one or more items to an album"
|
||||
},
|
||||
"addItem": "{count, plural, =1 {Dodaj element} few {Dodaj elementy} many {Dodaj elementów} other {Dodaj elementów}}",
|
||||
"@addItem": {
|
||||
"description": "Page title while adding one or more items to album"
|
||||
},
|
||||
"createOrSelectAlbum": "Utwórz lub wybierz album",
|
||||
"selectAlbum": "Wybierz album",
|
||||
"searchByAlbumNameHint": "Nazwa albumu",
|
||||
@@ -876,6 +899,7 @@
|
||||
"authToViewYourMemories": "Prosimy uwierzytelnić się, aby wyświetlić swoje wspomnienia",
|
||||
"unlock": "Odblokuj",
|
||||
"freeUpSpace": "Zwolnij miejsce",
|
||||
"freeUpSpaceSaving": "{count, plural, =1 {Może zostać usunięty z urządzenia, aby zwolnić {formattedSize}} many {Może być usuniętych z urządzenia, aby zwolnić {formattedSize}} other {Mogą być usunięte z urządzenia, aby zwolnić {formattedSize}}}",
|
||||
"filesBackedUpInAlbum": "{count, plural, one {1 plikowi} other {{formattedNumber} plikom}} w tym albumie została bezpiecznie utworzona kopia zapasowa",
|
||||
"@filesBackedUpInAlbum": {
|
||||
"description": "Text to tell user how many files have been backed up in the album",
|
||||
@@ -906,6 +930,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@freeUpSpaceSaving": {
|
||||
"description": "Text to tell user how much space they can free up by deleting items from the device"
|
||||
},
|
||||
"freeUpAccessPostDelete": "Nadal możesz mieć dostęp {count, plural, =1 {do tego} other {do tych}} na Ente tak długo, jak masz aktywną subskrypcję",
|
||||
"@freeUpAccessPostDelete": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"freeUpAmount": "Zwolnij {sizeInMBorGB}",
|
||||
"thisEmailIsAlreadyInUse": "Ten e-mail jest już używany",
|
||||
"incorrectCode": "Nieprawidłowy kod",
|
||||
@@ -1233,6 +1269,8 @@
|
||||
"description": "Subtitle to indicate that the user can find people quickly by name"
|
||||
},
|
||||
"findPeopleByName": "Szybko szukaj osób po imieniu",
|
||||
"addViewers": "{count, plural, =0 {Dodaj widza} =1 {Dodaj widza} other {Dodaj widzów}}",
|
||||
"addCollaborators": "{count, plural, =0 {Dodaj współuczestnika} =1 {Dodaj współuczestnika} other {Dodaj współuczestników}}",
|
||||
"longPressAnEmailToVerifyEndToEndEncryption": "Naciśnij i przytrzymaj e-mail, aby zweryfikować szyfrowanie end-to-end.",
|
||||
"developerSettingsWarning": "Czy na pewno chcesz zmodyfikować ustawienia programisty?",
|
||||
"developerSettings": "Ustawienia dla programistów",
|
||||
@@ -1365,6 +1403,16 @@
|
||||
"enableMachineLearningBanner": "Włącz nauczanie maszynowe dla magicznego wyszukiwania i rozpoznawania twarzy",
|
||||
"searchDiscoverEmptySection": "Obrazy będą wyświetlane tutaj po zakończeniu przetwarzania i synchronizacji",
|
||||
"searchPersonsEmptySection": "Osoby będą wyświetlane tutaj po zakończeniu przetwarzania i synchronizacji",
|
||||
"viewersSuccessfullyAdded": "{count, plural, =0 {Dodano 0 widzów} =1 {Dodano 1 widza} other {Dodano {count} widzów}}",
|
||||
"@viewersSuccessfullyAdded": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
},
|
||||
"description": "Number of viewers that were successfully added to an album."
|
||||
},
|
||||
"collaboratorsSuccessfullyAdded": "{count, plural, =0 {Dodano 0 współuczestników} =1 {Dodano 1 współuczestnika} other {Dodano {count} współuczestników}}",
|
||||
"@collaboratorsSuccessfullyAdded": {
|
||||
"placeholders": {
|
||||
@@ -1440,6 +1488,15 @@
|
||||
},
|
||||
"currentlyRunning": "aktualnie uruchomiony",
|
||||
"ignored": "ignorowane",
|
||||
"photosCount": "{count, plural, =0 {0 zdjęć} =1 {1 zdjęcie} few {{count} zdjęcia} many {{count} zdjęć} other {{count} zdjęć}}",
|
||||
"@photosCount": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"file": "Plik",
|
||||
"searchSectionsLengthMismatch": "Niezgodność długości sekcji: {snapshotLength} != {searchLength}",
|
||||
"@searchSectionsLengthMismatch": {
|
||||
@@ -1539,6 +1596,7 @@
|
||||
"joinAlbumSubtextViewer": "aby dodać to do udostępnionych albumów",
|
||||
"join": "Dołącz",
|
||||
"linkEmail": "Połącz adres e-mail",
|
||||
"link": "Połącz",
|
||||
"noEnteAccountExclamation": "Brak konta Ente!",
|
||||
"orPickFromYourContacts": "lub wybierz ze swoich kontaktów",
|
||||
"emailDoesNotHaveEnteAccount": "{email} nie posiada konta Ente.",
|
||||
@@ -1559,6 +1617,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"reassignMe": "Przypisz \"Mnie\" ponownie",
|
||||
"me": "Ja",
|
||||
"linkEmailToContactBannerCaption": "aby szybciej udostępniać",
|
||||
"@linkEmailToContactBannerCaption": {
|
||||
@@ -1586,6 +1645,7 @@
|
||||
}
|
||||
},
|
||||
"selectYourFace": "Wybierz swoją twarz",
|
||||
"reassigningLoading": "Ponowne przypisywanie...",
|
||||
"reassignedToName": "Ponownie przypisano cię do {name}",
|
||||
"@reassignedToName": {
|
||||
"placeholders": {
|
||||
@@ -1597,12 +1657,19 @@
|
||||
"saveChangesBeforeLeavingQuestion": "Zapisać zmiany przed wyjściem?",
|
||||
"dontSave": "Nie zapisuj",
|
||||
"thisIsMeExclamation": "To ja!",
|
||||
"linkPerson": "Połącz osobę",
|
||||
"linkPersonCaption": "dla lepszych doświadczeń podczas udostępniania",
|
||||
"@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": "Streamowalne wideo",
|
||||
"processingVideos": "Przetwarzanie wideo",
|
||||
"streamDetails": "Szczegóły transmisji",
|
||||
"processing": "Przetwarzanie",
|
||||
"queued": "W kolejce",
|
||||
"ineligible": "Nie kwalifikuje się",
|
||||
"failed": "Nie powiodło się",
|
||||
"playStream": "Odtwórz transmisję",
|
||||
"playOriginal": "Odtwórz oryginał",
|
||||
"joinAlbumConfirmationDialogBody": "Dołączenie do albumu sprawi, że Twój e-mail będzie widoczny dla jego uczestników.",
|
||||
"pleaseWaitThisWillTakeAWhile": "Prosimy czekać, to może zająć chwilę.",
|
||||
@@ -1619,13 +1686,25 @@
|
||||
"moveSelectedPhotosToOneDate": "Przenieś wybrane zdjęcia na jedną datę",
|
||||
"shiftDatesAndTime": "Zmień daty i czas",
|
||||
"photosKeepRelativeTimeDifference": "Zdjęcia zachowują względną różnicę czasu",
|
||||
"photocountPhotos": "{count, plural, =0 {Brak zdjęć} =1 {1 zdjęcie} few {{count} zdjęcia} many {{count} zdjęć} other {{count} zdjęć}}",
|
||||
"@photocountPhotos": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int",
|
||||
"example": "2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appIcon": "Ikona aplikacji",
|
||||
"notThisPerson": "Nie ta osoba?",
|
||||
"selectedItemsWillBeRemovedFromThisPerson": "Wybrane elementy zostaną usunięte z tej osoby, ale nie zostaną usunięte z Twojej biblioteki.",
|
||||
"throughTheYears": "{dateFormat} przez lata",
|
||||
"thisWeekThroughTheYears": "Ten tydzień przez lata",
|
||||
"thisWeekXYearsAgo": "{count, plural, =1 {W tym tygodniu, {count} rok temu} few {W tym tygodniu, {count} lata temu} many {W tym tygodniu, {count} lat temu} other {W tym tygodniu, {count} lat temu}}",
|
||||
"youAndThem": "Ty i {name}",
|
||||
"admiringThem": "Podziwianie {name}",
|
||||
"embracingThem": "Obejmowanie {name}",
|
||||
"partyWithThem": "Impreza z {name}",
|
||||
"hikingWithThem": "Wędrówka z {name}",
|
||||
"feastingWithThem": "Ucztowanie z {name}",
|
||||
"selfiesWithThem": "Selfie z {name}",
|
||||
@@ -1638,6 +1717,7 @@
|
||||
"personIsAge": "{name} ma {age} lat!",
|
||||
"personTurningAge": "{name} wkrótce będzie mieć {age} lat",
|
||||
"lastTimeWithThem": "Ostatnio z {name}",
|
||||
"tripToLocation": "Wyjazd do {location}",
|
||||
"tripInYear": "Podróż w {year}",
|
||||
"lastYearsTrip": "Zeszłoroczna podróż",
|
||||
"sunrise": "Na horyzoncie",
|
||||
@@ -1655,6 +1735,7 @@
|
||||
"peopleWidgetDesc": "Wybierz osoby, które chcesz zobaczyć na ekranie głównym.",
|
||||
"albumsWidgetDesc": "Wybierz albumy, które chcesz zobaczyć na ekranie głównym.",
|
||||
"memoriesWidgetDesc": "Wybierz rodzaj wspomnień, które chcesz zobaczyć na ekranie głównym.",
|
||||
"smartMemories": "Inteligentne wspomnienia",
|
||||
"pastYearsMemories": "Wspomnienia z ubiegłych lat",
|
||||
"deleteMultipleAlbumDialog": "Usunąć również zdjęcia (i filmy) obecne w tych albumach {count} z <bold>wszystkich</bold> innych albumów, których są częścią?",
|
||||
"addParticipants": "Dodaj uczestników",
|
||||
@@ -1695,7 +1776,56 @@
|
||||
"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ń",
|
||||
"thisMonth": "Ten miesiąc",
|
||||
"thisYear": "Ten rok",
|
||||
"groupBy": "Grupuj według",
|
||||
"faceThumbnailGenerationFailed": "Nie można wygenerować miniaturek twarzy",
|
||||
"fileAnalysisFailed": "Nie można przeanalizować pliku"
|
||||
"fileAnalysisFailed": "Nie można przeanalizować pliku",
|
||||
"editAutoAddPeople": "Edytuj automatyczne dodawanie osób",
|
||||
"autoAddPeople": "Automatycznie dodaj osoby",
|
||||
"autoAddToAlbum": "Automatycznie dodaj do albumu",
|
||||
"shouldRemoveFilesSmartAlbumsDesc": "Czy pliki związane z osobą, które zostały wcześniej wybrane w inteligentnych albumach, powinny zostać usunięte?",
|
||||
"addingPhotos": "Dodawanie zdjęć",
|
||||
"gettingReady": "Przygotowywanie",
|
||||
"addSomePhotosDesc1": "Dodaj jakieś zdjęcia lub wybierz ",
|
||||
"addSomePhotosDesc2": "znane twarze",
|
||||
"addSomePhotosDesc3": "\nzaczynając od",
|
||||
"ignorePerson": "Ignoruj osobę",
|
||||
"mixedGrouping": "Grupowanie mieszane?",
|
||||
"analysis": "Analiza",
|
||||
"doesGroupContainMultiplePeople": "Czy ta grupa zawiera wiele osób?",
|
||||
"automaticallyAnalyzeAndSplitGrouping": "Automatycznie przeanalizujemy grupę, aby ustalić, czy jest wiele osób i oddzielimy ich ponownie. Może to potrwać kilka sekund.",
|
||||
"layout": "Układ",
|
||||
"day": "Dzień",
|
||||
"peopleAutoAddDesc": "Wybierz osoby, które chcesz automatycznie dodać do albumu",
|
||||
"undo": "Cofnij",
|
||||
"redo": "Ponów",
|
||||
"filter": "Filtruj",
|
||||
"adjust": "Dostosuj",
|
||||
"draw": "Rysuj",
|
||||
"sticker": "Naklejka",
|
||||
"brushColor": "Kolor Pędzla",
|
||||
"font": "Czcionka",
|
||||
"background": "Tło",
|
||||
"align": "Wyrównaj",
|
||||
"addedToAlbums": "{count, plural, =1{Pomyślnie dodano do 1 albumu} other{Pomyślnie dodano do {count} albumów}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1778,5 +1778,12 @@
|
||||
"sameperson": "Тот же человек?",
|
||||
"indexingPausedStatusDescription": "Индексирование приостановлено. Оно автоматически возобновится, когда устройство будет готово. Устройство считается готовым, когда уровень заряда батареи, её состояние и температура находятся в пределах нормы.",
|
||||
"faceThumbnailGenerationFailed": "Не удалось создать миниатюры лиц",
|
||||
"fileAnalysisFailed": "Не удалось проанализировать файл"
|
||||
"fileAnalysisFailed": "Не удалось проанализировать файл",
|
||||
"addingPhotos": "Добавление фото",
|
||||
"gettingReady": "Идет подготовка",
|
||||
"addSomePhotosDesc2": "знакомые лица",
|
||||
"analysis": "Анализ",
|
||||
"day": "День",
|
||||
"filter": "Фильтр",
|
||||
"font": "Шрифт"
|
||||
}
|
||||
@@ -141,6 +141,66 @@
|
||||
"enterThe6digitCodeFromnyourAuthenticatorApp": "Унесите 6-цифрени кôд из\nапликације за аутентификацију",
|
||||
"confirm": "Потврди",
|
||||
"setupComplete": "Постављање завршено",
|
||||
"saveYourRecoveryKeyIfYouHaventAlready": "Уколико већ нисте, сачувајте кључ за опоравак",
|
||||
"thisCanBeUsedToRecoverYourAccountIfYou": "Може се користити за опоравак рачуна уколико заборавите други ниво провере идентитета",
|
||||
"twofactorAuthenticationPageTitle": "Двострука провера идентитета",
|
||||
"lostDevice": "Изгубили сте уређај?",
|
||||
"verifyingRecoveryKey": "Проверавам кључ за опоравак...",
|
||||
"recoveryKeyVerified": "Кључ за опоравак је потврђен",
|
||||
"recoveryKeySuccessBody": "Сјајно! Ваш кључ за опоравак је исправан. Хвала за потврду.\n\nНе заборавите да овај кључ чувате на сигурном месту.",
|
||||
"invalidRecoveryKey": "Кључ за опоравак који сте унели није исправан. Постарајте се да садржи 24 речи, и проверите да ли сте их правилно уписали.\n\nУколико сте унели стари формат кључа, постарајте се да садржи 64 карактера и додатно проверите да ли је сваки исправно уписан.",
|
||||
"invalidKey": "Неисправан кључ",
|
||||
"tryAgain": "Покушај поново",
|
||||
"viewRecoveryKey": "Погледај кључ за опоравак",
|
||||
"confirmRecoveryKey": "Потврди кључ за опоравак",
|
||||
"recoveryKeyVerifyReason": "Кључ за опоравак рачуна је једини начин да повратите фотографије ако заборавите лозинку. Можете га пронаћи под Подешавања > Рачун.\n\nУнесите ваш кључ за опоравак рачуна како бисмо били сигурни да сте га правилно сачували.",
|
||||
"confirmYourRecoveryKey": "Потврдите кључ за опоравак",
|
||||
"addViewer": "Додај посматрача",
|
||||
"addCollaborator": "Додај сарадника",
|
||||
"addANewEmail": "Додај нови имејл",
|
||||
"orPickAnExistingOne": "Или изабери већ постојећи",
|
||||
"collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": "Сарадници могу додати фотографије и клипове у заједнички албум.",
|
||||
"enterEmail": "Унеси имејл",
|
||||
"albumOwner": "Власник",
|
||||
"@albumOwner": {
|
||||
"description": "Role of the album owner"
|
||||
},
|
||||
"you": "Ти",
|
||||
"collaborator": "Сарадник",
|
||||
"addMore": "Додај још",
|
||||
"@addMore": {
|
||||
"description": "Button text to add more collaborators/viewers"
|
||||
},
|
||||
"viewer": "Посматрач",
|
||||
"remove": "Уклони",
|
||||
"removeParticipant": "Уклони учесника",
|
||||
"@removeParticipant": {
|
||||
"description": "menuSectionTitle for removing a participant"
|
||||
},
|
||||
"manage": "Управљај",
|
||||
"addedAs": "Додат као",
|
||||
"changePermissions": "Измените дозволе?",
|
||||
"yesConvertToViewer": "Да, претвори у посматрача",
|
||||
"cannotAddMorePhotosAfterBecomingViewer": "{user} неће више моћи да додаје фотографије у овај албум\nИ даље ће моћи да уклони фотографије које је претходно додао",
|
||||
"allowAddingPhotos": "Дозволи додавање фотографија",
|
||||
"@allowAddingPhotos": {
|
||||
"description": "Switch button to enable uploading photos to a public link"
|
||||
},
|
||||
"allowAddPhotosDescription": "Дозволи људима са линком да додају фотографије у заједнички албум.",
|
||||
"passwordLock": "Закључај помоћу лозинке",
|
||||
"canNotOpenTitle": "Не могу да отворим овај албум",
|
||||
"canNotOpenBody": "Жао ми је, овај албум се не може отворити у апликацији.",
|
||||
"disableDownloadWarningTitle": "Молим, обратите пажњу",
|
||||
"disableDownloadWarningBody": "Људи и даље могу да направе снимак екрана или да скину ваше фотографије користећи друге апликације",
|
||||
"allowDownloads": "Дозволи преузимање",
|
||||
"linkExpiry": "Рок важења линка",
|
||||
"linkExpired": "Истекао",
|
||||
"linkEnabled": "Омогућено",
|
||||
"linkNeverExpires": "Никада",
|
||||
"expiredLinkInfo": "Линк је истекао. Поставите ново време за престанак важности линка или онемогућите његово истицање.",
|
||||
"setAPassword": "Постави лозинку",
|
||||
"lockButtonLabel": "Закључај",
|
||||
"enterPassword": "Унеси лозинку",
|
||||
"removeLink": "Уклони везу",
|
||||
"manageLink": "Управљај везом",
|
||||
"linkExpiresOn": "Веза ће истећи {expiryTime}",
|
||||
@@ -219,6 +279,49 @@
|
||||
"codeChangeLimitReached": "Жао нам је, достигли сте максимум броја промена кôда.",
|
||||
"onlyFamilyAdminCanChangeCode": "Молимо вас да контактирате {familyAdminEmail} да бисте променили свој код.",
|
||||
"storageInGB": "{storageAmountInGB} ГБ",
|
||||
"claimed": "Освојено",
|
||||
"@claimed": {
|
||||
"description": "Used to indicate storage claimed, like 10GB Claimed"
|
||||
},
|
||||
"details": "Више детаља",
|
||||
"claimMore": "Освоји још!",
|
||||
"theyAlsoGetXGb": "Они ће такође добити {storageAmountInGB} ГБ",
|
||||
"freeStorageOnReferralSuccess": "{storageAmountInGB} ГБ сваки пут када се неко претплати на неку од наших понуда и при том унесе ваш код",
|
||||
"claimFreeStorage": "Освоји бесплатан простор на диску",
|
||||
"inviteYourFriends": "Позови своје пријатеље",
|
||||
"referralStep1": "1. Дај овај код својим пријатељима",
|
||||
"referralStep2": "2. Они се претплате на понуду која се плаћа",
|
||||
"referralStep3": "3. Обоје добијате {storageInGB} ГБ* бесплатно",
|
||||
"referralsAreCurrentlyPaused": "Препоручивање је тренутно паузирано",
|
||||
"youCanAtMaxDoubleYourStorage": "* У најбољем случају можете удвостручити ваш простор",
|
||||
"faq": "Честа питања",
|
||||
"help": "Помоћ",
|
||||
"oopsSomethingWentWrong": "Упс, дошло је до грешке",
|
||||
"peopleUsingYourCode": "Људи који користе ваш позивни код",
|
||||
"total": "укупно",
|
||||
"codeUsedByYou": "Код који ви користите",
|
||||
"freeStorageClaimed": "Освојен бесплатан простор",
|
||||
"freeStorageUsable": "Бесплатан простор који је могуће искористити",
|
||||
"removeFromAlbumTitle": "Уклони из албума?",
|
||||
"removeFromAlbum": "Уклони из албума",
|
||||
"itemsWillBeRemovedFromAlbum": "Изабране ставке ће бити уклоњене из овог албума",
|
||||
"addingToFavorites": "Додајем у омиљене",
|
||||
"removingFromFavorites": "Уклањам из омиљених",
|
||||
"sorryCouldNotAddToFavorites": "Извини, није додато у омиљене!",
|
||||
"sorryCouldNotRemoveFromFavorites": "Извини, није уклоњено из омиљених!",
|
||||
"subscribe": "Претплати се",
|
||||
"deleteSharedAlbum": "Иѕбриши дељени албум?",
|
||||
"deleteAlbum": "Избриши албум",
|
||||
"deleteSharedAlbumDialogBody": "Албум ће бити обрисан за све",
|
||||
"yesRemove": "Да, уклони",
|
||||
"creatingLink": "Креирам линк...",
|
||||
"removeWithQuestionMark": "Уклони?",
|
||||
"removeParticipantBody": "{userEmail} ће бити уклоњен из овог дељеног албума.\n\nСве фотографије које су они додали ће такође бити уклоњене",
|
||||
"keepPhotos": "Задржи фотографије",
|
||||
"deletePhotos": "Обриши фотгорафије",
|
||||
"inviteToEnte": "Позови у Енте",
|
||||
"disableLinkMessage": "Ово ће уклонити јавни линк за приступ \"{albumName}\".",
|
||||
"sharing": "Делим...",
|
||||
"youCannotShareWithYourself": "Не можеш делити сам са собом",
|
||||
"archive": "Архивирај",
|
||||
"createAlbumActionHint": "Дуго притисните да бисте изабрали фотографије и кликните на + да бисте направили албум",
|
||||
@@ -286,5 +389,536 @@
|
||||
"advancedSettings": "Напредно",
|
||||
"@advancedSettings": {
|
||||
"description": "The text to display in the advanced settings section"
|
||||
},
|
||||
"photoGridSize": "Величина мреже за приказ фотографија",
|
||||
"manageDeviceStorage": "Управљај кеш меморијом уређаја",
|
||||
"manageDeviceStorageDesc": "Прегледај и очисти локалну кеш меморију.",
|
||||
"machineLearning": "Машинско учење",
|
||||
"mlConsent": "Омогући машинско учење",
|
||||
"mlConsentTitle": "Омогући машинско учење?",
|
||||
"mlConsentPrivacy": "Кликните како бисте сазнали више о овоме у нашим правилима приватности",
|
||||
"mlConsentConfirmation": "Разумем, и желим да укључим машинско учење",
|
||||
"magicSearch": "Магична претрага",
|
||||
"discover": "Истражи",
|
||||
"@discover": {
|
||||
"description": "The text to display for the discover section under which we show receipts, screenshots, sunsets, greenery, etc."
|
||||
},
|
||||
"discover_identity": "Идентитет",
|
||||
"discover_screenshots": "Снимци екрана",
|
||||
"discover_receipts": "Признанице",
|
||||
"discover_notes": "Белешке",
|
||||
"discover_memes": "Мимови",
|
||||
"discover_visiting_cards": "Разгледнице",
|
||||
"discover_babies": "Бебе",
|
||||
"discover_pets": "Љубимци",
|
||||
"discover_selfies": "Селфији",
|
||||
"discover_wallpapers": "Позадине",
|
||||
"discover_food": "Храна",
|
||||
"discover_celebrations": "Прославе",
|
||||
"discover_sunset": "Заласци сунца",
|
||||
"discover_hills": "Брда",
|
||||
"discover_greenery": "Зеленило",
|
||||
"waitingForWifi": "Чекам на бежичну мрежу...",
|
||||
"status": "Статус",
|
||||
"indexedItems": "Индексиране ставке",
|
||||
"pendingItems": "На чекању",
|
||||
"clearIndexes": "Очисти индексе",
|
||||
"selectFoldersForBackup": "Изабери фолдере за копирање",
|
||||
"selectedFoldersWillBeEncryptedAndBackedUp": "Иѕабрани фолдери ће бити енкриптовани и сигурно похрањени",
|
||||
"unselectAll": "Деселектуј све",
|
||||
"selectAll": "Означи све",
|
||||
"skip": "Прескочи",
|
||||
"updatingFolderSelection": "Освежавам избор фолдера...",
|
||||
"duplicateItemsGroup": "{count} фајлова, {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": "Прикажи успомене",
|
||||
"yearsAgo": "{count, plural, one{{count} година уназад} few {{count} године уназад} other{{count} година уназад}}",
|
||||
"backupStatus": "Статус резервних копија",
|
||||
"backupOverMobileData": "Копирај користећи мобилни интернет",
|
||||
"backupVideos": "Копирај видео снимке",
|
||||
"disableAutoLock": "Онемогући закључавање екрана",
|
||||
"about": "О нама",
|
||||
"privacy": "Приватност",
|
||||
"terms": "Услови",
|
||||
"checkForUpdates": "Провери да ли постоје новине",
|
||||
"checkStatus": "Провери статус",
|
||||
"checking": "Проверавам...",
|
||||
"youAreOnTheLatestVersion": "Користите најновију верзију",
|
||||
"account": "Рачун",
|
||||
"manageSubscription": "Управљај претплатом",
|
||||
"changePassword": "Измени лозинку",
|
||||
"authToChangeYourPassword": "Потврдите идентитет како бисте променили лозинку",
|
||||
"emailVerificationToggle": "Провера имејла",
|
||||
"exportYourData": "Извези своје податке",
|
||||
"logout": "Одјави се",
|
||||
"authToInitiateAccountDeletion": "Потврдите идентитет како бисте покренули брисање рачуна",
|
||||
"areYouSureYouWantToLogout": "Да ли сигурно желите да се одјавите?",
|
||||
"yesLogout": "Да, одјави ме",
|
||||
"aNewVersionOfEnteIsAvailable": "Нова Енте верзија је доступна.",
|
||||
"update": "Ажурирај",
|
||||
"installManually": "Инсталирај ручно",
|
||||
"criticalUpdateAvailable": "Критично ажурирање је доступно",
|
||||
"updateAvailable": "Ажурирање доступно",
|
||||
"ignoreUpdate": "Игнориши",
|
||||
"cannotDeleteSharedFiles": "Не могу да обришем дељене фајлове",
|
||||
"retry": "Покушај поново",
|
||||
"freeUpDeviceSpace": "Ослободи простор на уређају",
|
||||
"allClear": "✨ Све је чисто",
|
||||
"noDeviceThatCanBeDeleted": "Не постоје фајлови на овом уређају који би могли бити обрисани",
|
||||
"removeDuplicates": "Уклони дупликате",
|
||||
"removeDuplicatesDesc": "Прегледај и уклони фајлове који су дупликати.",
|
||||
"viewLargeFiles": "Велики фајлови",
|
||||
"viewLargeFilesDesc": "Погледај фајлове који заузимају највише простора.",
|
||||
"noDuplicates": "✨ Нема дупликата",
|
||||
"youveNoDuplicateFilesThatCanBeCleared": "Немаш дупликата које бисмо могли да обришемо",
|
||||
"rateUs": "Оцени нас",
|
||||
"youHaveSuccessfullyFreedUp": "Успешно сте ослободили {storageSaved}!",
|
||||
"@youHaveSuccessfullyFreedUp": {
|
||||
"description": "The text to display when the user has successfully freed up storage",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"storageSaved": {
|
||||
"example": "1.2 GB",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"duplicateFileCountWithStorageSaved": "Обрисали сте {count, plural, one{{count} дупликат} few {{count} дупликата} other{{count} дупликата}}, ослобађам ({storageSaved}!)",
|
||||
"@duplicateFileCountWithStorageSaved": {
|
||||
"description": "The text to display when the user has successfully cleaned up duplicate files",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"example": "1",
|
||||
"type": "int"
|
||||
},
|
||||
"storageSaved": {
|
||||
"example": "1.2 GB",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"familyPlans": "Породичне понуде",
|
||||
"referrals": "Препоруке",
|
||||
"notifications": "Обавештења",
|
||||
"sharedPhotoNotifications": "Нове дељене фотографије",
|
||||
"sharedPhotoNotificationsExplanation": "Добиј обавештење када неко дода фотографију у албум у којем ви учествујете",
|
||||
"advanced": "Напредно",
|
||||
"general": "Опште",
|
||||
"security": "Сигурност",
|
||||
"no": "Не",
|
||||
"yes": "Да",
|
||||
"rateUsOnStore": "Оцени нас на {storeName}",
|
||||
"blog": "Блог",
|
||||
"merchandise": "Роба",
|
||||
"twitter": "Твитер",
|
||||
"mastodon": "Мастодон",
|
||||
"matrix": "Матрикс",
|
||||
"discord": "Дискорд",
|
||||
"reddit": "Редит",
|
||||
"yourStorageDetailsCouldNotBeFetched": "Подаци о вашем простору на диску нису могли бити освежени",
|
||||
"reportABug": "Пријави грешку",
|
||||
"reportBug": "Пријави грешку",
|
||||
"suggestFeatures": "Предложи нову функционалност",
|
||||
"support": "Подршка",
|
||||
"theme": "Тема",
|
||||
"lightTheme": "Светла",
|
||||
"darkTheme": "Тамна",
|
||||
"systemTheme": "Системска",
|
||||
"freeTrial": "Бесплатна проба",
|
||||
"selectYourPlan": "Изабери пакет",
|
||||
"enteSubscriptionPitch": "Енте чува твоје успомене тако да су увек са тобом, чак и ако изгубиш уређај.",
|
||||
"enteSubscriptionShareWithFamily": "Можеш додати и породицу у оквиру овог пакета.",
|
||||
"currentUsageIs": "Тренутно искоришћено",
|
||||
"@currentUsageIs": {
|
||||
"description": "This text is followed by storage usage",
|
||||
"examples": {
|
||||
"0": "Current usage is 1.2 GB"
|
||||
},
|
||||
"type": "text"
|
||||
},
|
||||
"faqs": "Често постављана питања",
|
||||
"renewsOn": "Претплата се обнавља {endDate}",
|
||||
"freeTrialValidTill": "Бесплатан пробни период важи до {endDate}",
|
||||
"validTill": "Важи до {endDate}",
|
||||
"addOnValidTill": "Ваш {storageAmount} додатак важи до {endDate}",
|
||||
"playStoreFreeTrialValidTill": "Бесплатни пробни период важи до {endDate}.\nМожете изабрати неки од планова након тога.",
|
||||
"subWillBeCancelledOn": "Ваша претплата ће бити отказана {endDate}",
|
||||
"subscription": "Претплата",
|
||||
"paymentDetails": "Детаљи плаћања",
|
||||
"manageFamily": "Управљај породицом",
|
||||
"renewSubscription": "Однови претплату",
|
||||
"cancelSubscription": "Откажи претплату",
|
||||
"areYouSureYouWantToRenew": "Да ли сте сигурни да желите да обновите претплату?",
|
||||
"yesRenew": "Да, обнови претплату",
|
||||
"areYouSureYouWantToCancel": "Да ли сте сигурни да желите да откажете?",
|
||||
"yesCancel": "Да, откажи",
|
||||
"failedToCancel": "Неуспешно отказивање",
|
||||
"twoMonthsFreeOnYearlyPlans": "2 месеца гратис уз годишњу претплату",
|
||||
"monthly": "Месечно",
|
||||
"@monthly": {
|
||||
"description": "The text to display for monthly plans",
|
||||
"type": "text"
|
||||
},
|
||||
"yearly": "Годишње",
|
||||
"@yearly": {
|
||||
"description": "The text to display for yearly plans",
|
||||
"type": "text"
|
||||
},
|
||||
"confirmPlanChange": "Потврди промену пакета",
|
||||
"areYouSureYouWantToChangeYourPlan": "Да ли сигурно желиш да промениш пакет?",
|
||||
"youCannotDowngradeToThisPlan": "Не можете изабрати овај пакет",
|
||||
"cancelOtherSubscription": "Молимо, прво откажите претплау на {paymentProvider}",
|
||||
"@cancelOtherSubscription": {
|
||||
"description": "The text to display when the user has an existing subscription from a different payment provider",
|
||||
"type": "text",
|
||||
"placeholders": {
|
||||
"paymentProvider": {
|
||||
"example": "Apple",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"optionalAsShortAsYouLike": "Опционо, коико год кратко желите...",
|
||||
"send": "Пошаљи",
|
||||
"askCancelReason": "Отказали сте претплату. Да ли бисте нам рекли разлог?",
|
||||
"thankYouForSubscribing": "Хвала што сте се претплатили!",
|
||||
"yourPurchaseWasSuccessful": "Куповина успешно закључена",
|
||||
"yourPlanWasSuccessfullyUpgraded": "Успешно сте прешли на јачи пакет",
|
||||
"yourPlanWasSuccessfullyDowngraded": "Успешно сте прешли на слабији пакет",
|
||||
"yourSubscriptionWasUpdatedSuccessfully": "Претплата успешно ажурирана",
|
||||
"googlePlayId": "Гугл плеј идентификација",
|
||||
"appleId": "Епл идентификација",
|
||||
"playstoreSubscription": "Претплата путем Гугл продавнице",
|
||||
"appstoreSubscription": "Претплата путем Епл продавнице",
|
||||
"visitWebToManage": "Претплатом можете управљати на страници web.ente.io",
|
||||
"couldNotUpdateSubscription": "Неуспешно ажурирање претплате",
|
||||
"pleaseContactSupportAndWeWillBeHappyToHelp": "Контактирајте подршку на support@ente.io и радо ћемо вам помоћи!",
|
||||
"paymentFailed": "Неуспела уплата",
|
||||
"continueOnFreeTrial": "Настави бесплатни пробни период",
|
||||
"areYouSureYouWantToExit": "Сигурно желите да изађете?",
|
||||
"thankYou": "Хвала вам",
|
||||
"pleaseWaitForSometimeBeforeRetrying": "Мало сачекајте пре него што покушате поново",
|
||||
"youAreOnAFamilyPlan": "Ви сте на породичном пакету!",
|
||||
"leaveFamily": "Напусти породицу",
|
||||
"areYouSureThatYouWantToLeaveTheFamily": "Да ли заиста желиш да напустиш породични пакет?",
|
||||
"leave": "Напусти",
|
||||
"rateTheApp": "Оцените апликацију",
|
||||
"startBackup": "Започни сигурносно копирање",
|
||||
"preserveMore": "Сачувај још",
|
||||
"grantFullAccessPrompt": "Дозволите приступ свим фотографијама у подешавањима уређаја",
|
||||
"allowPermTitle": "Дозволи приступ фотографијама",
|
||||
"openSettings": "Отвори подешавања",
|
||||
"selectMorePhotos": "Изабери још фотографија",
|
||||
"existingUser": "Постојећи корисник",
|
||||
"privateBackups": "Приватни бекапи",
|
||||
"forYourMemories": "за твоје успомене",
|
||||
"safelyStored": "Сигурно похрањено",
|
||||
"atAFalloutShelter": "у подземном бункеру",
|
||||
"designedToOutlive": "Направљено да надживи",
|
||||
"available": "Доступно",
|
||||
"everywhere": "свуда",
|
||||
"newToEnte": "Нови на Енте",
|
||||
"pleaseLoginAgain": "Молимо, улогујте се поново",
|
||||
"autoLogoutMessage": "Излоговани сте услед техничке грешке. Извињавамо се за непријатност.",
|
||||
"yourSubscriptionHasExpired": "Ваша претплата је истекла",
|
||||
"storageLimitExceeded": "Расположив простор је прекорачен",
|
||||
"upgrade": "Ажурирај",
|
||||
"raiseTicket": "Пошаљи упит",
|
||||
"@raiseTicket": {
|
||||
"description": "Button text for raising a support tickets in case of unhandled errors during backup",
|
||||
"type": "text"
|
||||
},
|
||||
"backupFailed": "Неуспешна израда резервне копије",
|
||||
"sorryBackupFailedDesc": "Извините, нисмо успели да копирамо овај фајл тренутно. Покушаћемо касније.",
|
||||
"couldNotBackUpTryLater": "Нисмо могли да бекапујемо ваше податке.\nПокушаћемо касније.",
|
||||
"enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "Енте може да екриптује и сачува фајлове једино ако дозволите приступ њима",
|
||||
"pleaseGrantPermissions": "Молимо, дозволите приступ",
|
||||
"grantPermission": "Дозволите приступ",
|
||||
"privateSharing": "Приватно дељење",
|
||||
"shareOnlyWithThePeopleYouWant": "Дели само са људима са којима желиш",
|
||||
"usePublicLinksForPeopleNotOnEnte": "Користи јавне линкове за људима који нису на Енте",
|
||||
"allowPeopleToAddPhotos": "Дозволи људима да додају фотографије",
|
||||
"shareAnAlbumNow": "Подели албум сада",
|
||||
"collectEventPhotos": "Прикупи фотографије са догађаја",
|
||||
"@onDevice": {
|
||||
"description": "The text displayed above folders/albums stored on device",
|
||||
"type": "text"
|
||||
},
|
||||
"onDevice": "На уређају",
|
||||
"@onEnte": {
|
||||
"description": "The text displayed above albums backed up to Ente",
|
||||
"type": "text"
|
||||
},
|
||||
"onEnte": "У Енте",
|
||||
"name": "Име",
|
||||
"newest": "Најновије",
|
||||
"lastUpdated": "Последње ажурирано",
|
||||
"deleteEmptyAlbums": "Избриши празне албуме",
|
||||
"deleteEmptyAlbumsWithQuestionMark": "Обриши празне албуме?",
|
||||
"permanentlyDelete": "Трајно избриши",
|
||||
"canOnlyCreateLinkForFilesOwnedByYou": "Можете направити линк само за фајлове који су ваше власништво",
|
||||
"linkCopiedToClipboard": "Линк је копиран у меморију",
|
||||
"restore": "Поврати",
|
||||
"@restore": {
|
||||
"description": "Display text for an action which triggers a restore of item from trash",
|
||||
"type": "text"
|
||||
},
|
||||
"moveToAlbum": "Премести у албум",
|
||||
"unarchive": "Врати из архиве",
|
||||
"createCollage": "Направи колаж",
|
||||
"saveCollage": "Сачувај колаж",
|
||||
"addToEnte": "Додај у Енте",
|
||||
"addToAlbum": "Додај у албум",
|
||||
"delete": "Обриши",
|
||||
"hide": "Сакриј",
|
||||
"share": "Подели",
|
||||
"searchByAlbumNameHint": "Име албума",
|
||||
"albumTitle": "Наслов албума",
|
||||
"enterAlbumName": "Унесите име албума",
|
||||
"movingFilesToAlbum": "Премештам фајлове у албум...",
|
||||
"addedSuccessfullyTo": "Успешно додато у {albumName}",
|
||||
"movedSuccessfullyTo": "Успешно премештено у {albumName}",
|
||||
"thisAlbumAlreadyHDACollaborativeLink": "Овај албум већ има линк за сарадњу",
|
||||
"collaborativeLinkCreatedFor": "Линк за сарадњу креиран за {albumName}",
|
||||
"askYourLovedOnesToShare": "Замолите ваше вољене да деле",
|
||||
"invite": "Позови",
|
||||
"shareYourFirstAlbum": "Подели твој први албум",
|
||||
"sharedWith": "Подељено са {emailIDs}",
|
||||
"sharedWithMe": "Подељено са мном",
|
||||
"sharedByMe": "Ја поделио",
|
||||
"doubleYourStorage": "Удвостручи простор",
|
||||
"referFriendsAnd2xYourPlan": "Препоручи пријатељима и удвостручи тренутни пакет",
|
||||
"rename": "Преименуј",
|
||||
"thisEmailIsAlreadyInUse": "Имејл је већ у употреби",
|
||||
"yourVerificationCodeHasExpired": "Ваш код ѕа верификацију је истекао",
|
||||
"emailChangedTo": "Имејл промењен у {newEmail}",
|
||||
"allMemoriesPreserved": "Све успомене су сачуване",
|
||||
"loadingGallery": "Учитавам галерију",
|
||||
"syncing": "Синхронизујем...",
|
||||
"syncStopped": "Синхронизација прекинута",
|
||||
"archiving": "Архивирам...",
|
||||
"unarchiving": "Премештам из архиве...",
|
||||
"successfullyArchived": "Успешно архивирано",
|
||||
"successfullyUnarchived": "Успешно премештено из архиве",
|
||||
"renameFile": "Преименуј фајл",
|
||||
"enterFileName": "Именуј фајл",
|
||||
"filesDeleted": "Обрисани фајлови",
|
||||
"permanentlyDeleteFromDevice": "Трајно обриши са уређаја?",
|
||||
"language": "Језик",
|
||||
"selectLanguage": "Изабери језик",
|
||||
"locationName": "Назив локације",
|
||||
"addLocation": "Додај локацију",
|
||||
"groupNearbyPhotos": "Групиши околне фотографије",
|
||||
"kiloMeterUnit": "км",
|
||||
"addLocationButton": "Додај",
|
||||
"radius": "Пречник",
|
||||
"save": "Сачувај",
|
||||
"centerPoint": "Централна тачка",
|
||||
"pickCenterPoint": "Изабери централну тачку",
|
||||
"useSelectedPhoto": "Корисити изабрану фотографију",
|
||||
"edit": "Измени",
|
||||
"deleteLocation": "Обриши локацију",
|
||||
"rotateLeft": "Заротирај у лево",
|
||||
"flip": "Обрни у огледалу",
|
||||
"rotateRight": "Заротирај у десно",
|
||||
"saveCopy": "Сачувај копију",
|
||||
"light": "Осветљење",
|
||||
"color": "Боја",
|
||||
"yesDiscardChanges": "Да, одбаци измене",
|
||||
"doYouWantToDiscardTheEditsYouHaveMade": "Да ли желиш да одбациш направљене измене?",
|
||||
"saving": "Чувам...",
|
||||
"editsSaved": "Измене сачуване",
|
||||
"distanceInKMUnit": "км",
|
||||
"@distanceInKMUnit": {
|
||||
"description": "Unit for distance in km"
|
||||
},
|
||||
"dayToday": "Данас",
|
||||
"dayYesterday": "Јуче",
|
||||
"usedSpace": "Искоришћен простор",
|
||||
"storageBreakupFamily": "Породица",
|
||||
"storageBreakupYou": "Ти",
|
||||
"@storageBreakupYou": {
|
||||
"description": "Label to indicate how much storage you are using when you are part of a family plan"
|
||||
},
|
||||
"fileInfoAddDescHint": "Додај опис",
|
||||
"editLocationTagTitle": "Измени локацију",
|
||||
"setRadius": "Задај пречник",
|
||||
"map": "Мапа",
|
||||
"@map": {
|
||||
"description": "Label for the map view"
|
||||
},
|
||||
"maps": "Мапе",
|
||||
"enableMaps": "Омогући мапе",
|
||||
"addPhotos": "Додај фотографије",
|
||||
"zoomOutToSeePhotos": "Одзумирај да видиш фотографије",
|
||||
"inviteYourFriendsToEnte": "Позови твоје пријатеље на Енте",
|
||||
"addToHiddenAlbum": "Додај у тајни албум",
|
||||
"viewAddOnButton": "Погледај додатке",
|
||||
"addOns": "Додаци",
|
||||
"addOnPageSubtitle": "Детаљи додатака",
|
||||
"yourMap": "Твоја мапа",
|
||||
"searchHint4": "Локација",
|
||||
"faces": "Лица",
|
||||
"people": "Људи",
|
||||
"contacts": "Контакти",
|
||||
"signOutFromOtherDevices": "Излогујте се са осталих уређаја",
|
||||
"signOutOtherDevices": "Излогуј друге уређаје",
|
||||
"selectALocation": "Изабери локацију",
|
||||
"selectALocationFirst": "Прво изабери локацију",
|
||||
"changeLocationOfSelectedItems": "Измени локацију изабраних ставки?",
|
||||
"playOnTv": "Гледај албум на телевизору",
|
||||
"pair": "Упари",
|
||||
"deviceNotFound": "Уређај није пронађен",
|
||||
"joinDiscord": "Прикључи се Дискорду",
|
||||
"locations": "Локације",
|
||||
"developerSettings": "с",
|
||||
"rotate": "Ротирај",
|
||||
"left": "Лево",
|
||||
"right": "Десно",
|
||||
"whatsNew": "Шта је ново",
|
||||
"useAsCover": "Користи за позадину",
|
||||
"notPersonLabel": "Није {name}?",
|
||||
"@notPersonLabel": {
|
||||
"description": "Label to indicate that the person in the photo is not the person whose name is mentioned",
|
||||
"placeholders": {
|
||||
"name": {
|
||||
"content": "{name}",
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"panorama": "Панорама",
|
||||
"reenterPassword": "Поново унеси лозинку",
|
||||
"reenterPin": "Поново унеси пин",
|
||||
"deviceLock": "Закључавање уређаја",
|
||||
"setNewPassword": "Постави нову лозинку",
|
||||
"enterPin": "Унеси пин",
|
||||
"setNewPin": "Постави нови пин",
|
||||
"appLock": "Закључавање апликације",
|
||||
"tapToUnlock": "Додирните за откључавање",
|
||||
"tooManyIncorrectAttempts": "Превише погрешних покушаја",
|
||||
"autoLock": "Аутоматско закључавање",
|
||||
"immediately": "Одмах",
|
||||
"hideContent": "Сакриј садржај",
|
||||
"nameTheAlbum": "Именуј албум",
|
||||
"showPerson": "Покажи особу",
|
||||
"sort": "Сортирај",
|
||||
"mostRecent": "Најновије",
|
||||
"mostRelevant": "Најрелевантније",
|
||||
"personName": "Име особе",
|
||||
"addNewPerson": "Додај нову особу",
|
||||
"addNameOrMerge": "Додај име или споји",
|
||||
"mergeWithExisting": "Споји са постојећом",
|
||||
"newPerson": "Нова особа",
|
||||
"addName": "Додај име",
|
||||
"add": "Додај",
|
||||
"localIndexing": "Локално индексирање",
|
||||
"resetPerson": "Уклони",
|
||||
"month": "месец",
|
||||
"yearShort": "год",
|
||||
"@yearShort": {
|
||||
"description": "Appears in pricing page (/yr)"
|
||||
},
|
||||
"allow": "Дозволи",
|
||||
"acceptTrustInvite": "Прихвати позивницу",
|
||||
"declineTrustInvite": "Одбиј позивницу",
|
||||
"legacy": "Наслеђе",
|
||||
"trustedContacts": "Контакти од поверења",
|
||||
"warning": "Упозорење",
|
||||
"proceed": "Настави",
|
||||
"gallery": "Галерија",
|
||||
"linkEmail": "Повежи имејл",
|
||||
"me": "Ја",
|
||||
"thisIsMeExclamation": "Ово сам ја!",
|
||||
"playOriginal": "Пусти оригинал",
|
||||
"selectTime": "Изабери време",
|
||||
"selectDate": "Изабери датум",
|
||||
"appIcon": "Иконица апликације",
|
||||
"notThisPerson": "То није та особа?",
|
||||
"throughTheYears": "{dateFormat} кроз године",
|
||||
"thisWeekThroughTheYears": "Тренутна недеља кроз године",
|
||||
"youAndThem": "Ти и {name}",
|
||||
"partyWithThem": "Журка са {name}",
|
||||
"hikingWithThem": "Походништво са {name}",
|
||||
"selfiesWithThem": "Селфији са {name}",
|
||||
"posingWithThem": "Позирање са {name}",
|
||||
"backgroundWithThem": "Прелепи призори са {name}",
|
||||
"sportsWithThem": "Спорт са {name}",
|
||||
"roadtripWithThem": "Путовање са {name}",
|
||||
"spotlightOnYourself": "Сва светла на теби",
|
||||
"spotlightOnThem": "Сва светла на {name}",
|
||||
"personTurningAge": "{name} ускоро навршава {age}",
|
||||
"lastTimeWithThem": "Последњи пут са {name}",
|
||||
"tripToLocation": "Пут у {location}",
|
||||
"tripInYear": "Пут из {year}",
|
||||
"lastYearsTrip": "Прошлогодишње путовање",
|
||||
"sunrise": "На хирозонту",
|
||||
"mountains": "Преко брда",
|
||||
"greenery": "Зеленило",
|
||||
"beach": "Песак и море",
|
||||
"city": "У граду",
|
||||
"moon": "Под светлом Месеца",
|
||||
"onTheRoad": "На путу",
|
||||
"food": "Кулинарски ужици",
|
||||
"pets": "Чупави другари",
|
||||
"curatedMemories": "Пробране успомене",
|
||||
"widgets": "Виџети",
|
||||
"memories": "Успомене",
|
||||
"pastYearsMemories": "Прошлогодишње успомене",
|
||||
"addParticipants": "Додај учеснике",
|
||||
"onThisDayMemories": "Успомене на данашњи дан",
|
||||
"onThisDay": "На данашњи дан",
|
||||
"birthdayNotifications": "Обавешења о рођенданима",
|
||||
"happyBirthday": "Срећан рођендан! 🥳",
|
||||
"birthdays": "Рођендани",
|
||||
"wishThemAHappyBirthday": "Пожели {name} срећан рођендан! 🎉",
|
||||
"otherDetectedFaces": "Друга препозната лица",
|
||||
"areThey": "Да ли је то",
|
||||
"questionmark": "?",
|
||||
"saveAsAnotherPerson": "Сачувај као другу особу",
|
||||
"showLessFaces": "Прикажи мање лица",
|
||||
"showMoreFaces": "Прикажи још лица",
|
||||
"ignore": "Игнориши",
|
||||
"merge": "Споји",
|
||||
"reset": "Ресетуј",
|
||||
"areYouSureYouWantToMergeThem": "Да ли си сигуран да желиш да их спојиш?",
|
||||
"addSomePhotosDesc1": "Додај фотографије или изабери",
|
||||
"addSomePhotosDesc2": "позната лица",
|
||||
"addSomePhotosDesc3": "за почетак",
|
||||
"ignorePerson": "Игнориши особу",
|
||||
"analysis": "Анализа",
|
||||
"layout": "Распоред",
|
||||
"day": "Дан",
|
||||
"peopleAutoAddDesc": "Изабери људе за које желиш да буду аутоматски додати у албум",
|
||||
"filter": "Филтер",
|
||||
"adjust": "Прилагоди",
|
||||
"draw": "Нацртај",
|
||||
"sticker": "Налепница",
|
||||
"brushColor": "Боја четкице",
|
||||
"font": "Фонт",
|
||||
"background": "Позадина",
|
||||
"align": "Поравнај",
|
||||
"addedToAlbums": "{count, plural, =1{Успешно додано у 1 албум} other{Успешно додано у {count} албума}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"@@locale ": "en",
|
||||
"enterYourEmailAddress": "Ange din e-postadress",
|
||||
"enterYourNewEmailAddress": "Ange din nya e-postadress",
|
||||
"accountWelcomeBack": "Välkommen tillbaka!",
|
||||
"emailAlreadyRegistered": "E-postadress redan registrerad.",
|
||||
"emailNotRegistered": "E-postadressen är inte registrerad.",
|
||||
@@ -345,8 +346,20 @@
|
||||
"keepPhotos": "Behåll foton",
|
||||
"deletePhotos": "Radera foton",
|
||||
"inviteToEnte": "Bjud in till Ente",
|
||||
"removePublicLink": "Ta bort publik länk",
|
||||
"sharing": "Delar...",
|
||||
"youCannotShareWithYourself": "Du kan inte dela med dig själv",
|
||||
"archive": "Arkiv",
|
||||
"importing": "Importerar....",
|
||||
"failedToLoadAlbums": "Det gick inte att läsa in album",
|
||||
"hidden": "Dold",
|
||||
"authToViewYourHiddenFiles": "Vänligen autentisera för att visa dina dolda filer",
|
||||
"authToViewTrashedFiles": "Vänligen autentisera för att se dina kastade filer",
|
||||
"trash": "Papperskorg",
|
||||
"uncategorized": "Okategoriserade",
|
||||
"videoSmallCase": "video",
|
||||
"photoSmallCase": "foto",
|
||||
"singleFileDeleteHighlight": "Det kommer att tas bort från alla album.",
|
||||
"yesDelete": "Ja, radera",
|
||||
"deleteFromDevice": "Radera från enhet",
|
||||
"newAlbum": "Nytt album",
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
"deleteAccountFeedbackPrompt": "Chúng tôi rất tiếc khi thấy bạn rời đi. Vui lòng chia sẻ phản hồi của bạn để giúp chúng tôi cải thiện.",
|
||||
"feedback": "Phản hồi",
|
||||
"kindlyHelpUsWithThisInformation": "Mong bạn giúp chúng tôi thông tin này",
|
||||
"confirmDeletePrompt": "Có, tôi muốn xóa vĩnh viễn tài khoản này và tất cả dữ liệu của nó.",
|
||||
"confirmDeletePrompt": "Có, tôi muốn xóa vĩnh viễn tài khoản này và tất cả dữ liệu.",
|
||||
"confirmAccountDeletion": "Xác nhận xóa tài khoản",
|
||||
"deleteAccountPermanentlyButton": "Xóa tài khoản vĩnh viễn",
|
||||
"yourAccountHasBeenDeleted": "Tài khoản của bạn đã bị xóa",
|
||||
"selectReason": "Chọn lý do",
|
||||
"deleteReason1": "Nó thiếu một tính năng quan trọng mà tôi cần",
|
||||
"deleteReason2": "Ứng dụng hoặc một tính năng nhất định không hoạt động như tôi muốn",
|
||||
"deleteReason2": "Ứng dụng hoặc một tính năng không hoạt động như tôi muốn",
|
||||
"deleteReason3": "Tôi tìm thấy một dịch vụ khác mà tôi thích hơn",
|
||||
"deleteReason4": "Lý do không có trong danh sách",
|
||||
"sendEmail": "Gửi email",
|
||||
@@ -241,7 +241,7 @@
|
||||
"linkHasExpired": "Liên kết đã hết hạn",
|
||||
"publicLinkEnabled": "Liên kết công khai đã được bật",
|
||||
"shareALink": "Chia sẻ một liên kết",
|
||||
"sharedAlbumSectionDescription": "Tạo album chia sẻ và cộng tác với người dùng Ente khác, bao gồm cả người dùng các gói miễn phí.",
|
||||
"sharedAlbumSectionDescription": "Tạo album chia sẻ và cộng tác với người dùng Ente khác, bao gồm người dùng gói miễn phí.",
|
||||
"shareWithPeopleSectionTitle": "{numberOfPeople, plural, =0 {Chia sẻ với những người cụ thể} =1 {Chia sẻ với 1 người} other {Chia sẻ với {numberOfPeople} người}}",
|
||||
"@shareWithPeopleSectionTitle": {
|
||||
"placeholders": {
|
||||
@@ -293,7 +293,7 @@
|
||||
"theyAlsoGetXGb": "Họ cũng nhận được {storageAmountInGB} GB",
|
||||
"freeStorageOnReferralSuccess": "{storageAmountInGB} GB mỗi khi ai đó đăng ký gói trả phí và áp dụng mã của bạn",
|
||||
"shareTextReferralCode": "Mã giới thiệu Ente: {referralCode} \n\nÁp dụng nó trong Cài đặt → Chung → Giới thiệu để nhận thêm {referralStorageInGB} GB miễn phí sau khi bạn đăng ký gói trả phí\n\nhttps://ente.io",
|
||||
"claimFreeStorage": "Nhận thêm dung lượng miễn phí",
|
||||
"claimFreeStorage": "Nhận thêm dung lượng",
|
||||
"inviteYourFriends": "Mời bạn bè của bạn",
|
||||
"failedToFetchReferralDetails": "Không thể lấy thông tin giới thiệu. Vui lòng thử lại sau.",
|
||||
"referralStep1": "1. Đưa mã này cho bạn bè của bạn",
|
||||
@@ -562,7 +562,7 @@
|
||||
"referrals": "Giới thiệu",
|
||||
"notifications": "Thông báo",
|
||||
"sharedPhotoNotifications": "Ảnh chia sẻ mới",
|
||||
"sharedPhotoNotificationsExplanation": "Nhận thông báo khi ai đó thêm ảnh vào album chia sẻ mà bạn tham gia",
|
||||
"sharedPhotoNotificationsExplanation": "Nhận thông báo khi ai đó thêm ảnh vào album chia sẻ mà bạn tham gia.",
|
||||
"advanced": "Nâng cao",
|
||||
"general": "Chung",
|
||||
"security": "Bảo mật",
|
||||
@@ -822,8 +822,8 @@
|
||||
"sharedWith": "Chia sẻ với {emailIDs}",
|
||||
"sharedWithMe": "Chia sẻ với tôi",
|
||||
"sharedByMe": "Chia sẻ bởi tôi",
|
||||
"doubleYourStorage": "Gấp đôi dung lượng lưu trữ của bạn",
|
||||
"referFriendsAnd2xYourPlan": "Giới thiệu bạn bè và ×2 gói của bạn",
|
||||
"doubleYourStorage": "Nhân đôi dung lượng",
|
||||
"referFriendsAnd2xYourPlan": "Giới thiệu bạn bè để được ×2 dung lượng gói của bạn",
|
||||
"shareAlbumHint": "Mở album và nhấn nút chia sẻ ở góc trên bên phải để chia sẻ.",
|
||||
"itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Trên các mục là số ngày còn lại trước khi xóa vĩnh viễn",
|
||||
"trashDaysLeft": "{count, plural, =0 {Sắp xóa} =1 {1 ngày} other {{count} ngày}}",
|
||||
@@ -1330,7 +1330,7 @@
|
||||
},
|
||||
"enable": "Bật",
|
||||
"enabled": "Bật",
|
||||
"moreDetails": "Thêm chi tiết",
|
||||
"moreDetails": "Thông tin thêm",
|
||||
"enableMLIndexingDesc": "Ente hỗ trợ học máy trên-thiết-bị nhằm nhận diện khuôn mặt, tìm kiếm vi diệu và các tính năng tìm kiếm nâng cao khác",
|
||||
"magicSearchHint": "Tìm kiếm vi diệu cho phép tìm ảnh theo nội dung của chúng, ví dụ: 'xe hơi', 'xe hơi đỏ', 'Ferrari'",
|
||||
"panorama": "Panorama",
|
||||
@@ -1752,7 +1752,7 @@
|
||||
"addAlbumWidgetPrompt": "Thêm tiện ích album vào màn hình chính và quay lại đây để tùy chỉnh.",
|
||||
"addPeopleWidgetPrompt": "Thêm tiện ích người vào màn hình chính và quay lại đây để tùy chỉnh.",
|
||||
"birthdayNotifications": "Thông báo sinh nhật",
|
||||
"receiveRemindersOnBirthdays": "Nhắc khi đến sinh nhật của ai đó. Chạm vào thông báo sẽ đưa bạn đến ảnh của người sinh nhật.",
|
||||
"receiveRemindersOnBirthdays": "Nhắc khi đến sinh nhật của ai đó. Nhấn vào thông báo sẽ đưa bạn đến ảnh của người sinh nhật.",
|
||||
"happyBirthday": "Chúc mừng sinh nhật! 🥳",
|
||||
"birthdays": "Sinh nhật",
|
||||
"wishThemAHappyBirthday": "Chúc {name} sinh nhật vui vẻ! 🎉",
|
||||
|
||||
@@ -1776,7 +1776,56 @@
|
||||
"same": "相同",
|
||||
"different": "不同",
|
||||
"sameperson": "是同一个人?",
|
||||
"cLTitle1": "高级图像编辑器",
|
||||
"cLDesc1": "我们正在发布一款全新且高级的图像编辑器,新增更多裁剪框架、快速编辑的滤镜预设,以及包括饱和度、对比度、亮度、色温等在内的精细调整选项。新的编辑器还支持在照片上绘制和添加表情符号作为贴纸。",
|
||||
"cLTitle2": "智能相册",
|
||||
"cLDesc2": "您现在可以将所选人物的照片自动添加到任何相册。只需进入相册,从溢出菜单中选择“自动添加人物”。如果与共享相册一起使用,您可以零点击分享照片。",
|
||||
"cLTitle3": "改进的相册",
|
||||
"cLDesc3": "我们新增了按周、月、年对图库进行分组的功能。您现在可以通过这些新的分组选项以及自定义网格,定制图库的外观,完全按照您的喜好进行设置",
|
||||
"cLTitle4": "更快滚动",
|
||||
"cLDesc4": "除了多项后台改进以提升图库滚动体验外,我们还重新设计了滚动条,添加了标记功能,让您可以快速跳转到时间轴上的不同位置。",
|
||||
"indexingPausedStatusDescription": "索引已暂停。待设备准备就绪后,索引将自动恢复。当设备的电池电量、电池健康度和温度状态处于健康范围内时,设备即被视为准备就绪。",
|
||||
"thisWeek": "本周",
|
||||
"lastWeek": "上周",
|
||||
"thisMonth": "本月",
|
||||
"thisYear": "今年",
|
||||
"groupBy": "分组依据",
|
||||
"faceThumbnailGenerationFailed": "无法生成人脸缩略图",
|
||||
"fileAnalysisFailed": "无法分析文件"
|
||||
"fileAnalysisFailed": "无法分析文件",
|
||||
"editAutoAddPeople": "编辑自动添加人物",
|
||||
"autoAddPeople": "自动添加人物",
|
||||
"autoAddToAlbum": "自动添加到相册",
|
||||
"shouldRemoveFilesSmartAlbumsDesc": "应该移除之前在智能相册中选择的与该人相关的文件吗?",
|
||||
"addingPhotos": "正在添加照片",
|
||||
"gettingReady": "准备就绪",
|
||||
"addSomePhotosDesc1": "添加一些照片或者选择 ",
|
||||
"addSomePhotosDesc2": "熟悉的面孔",
|
||||
"addSomePhotosDesc3": "首先",
|
||||
"ignorePerson": "忽略此人",
|
||||
"mixedGrouping": "混合分组?",
|
||||
"analysis": "分析",
|
||||
"doesGroupContainMultiplePeople": "这个分组包含多个人吗?",
|
||||
"automaticallyAnalyzeAndSplitGrouping": "我们将自动分析分组以确定是否有多人在场,并再次将他们分开。这可能需要几秒钟。",
|
||||
"layout": "布局",
|
||||
"day": "日",
|
||||
"peopleAutoAddDesc": "选择您想要自动添加到相册的人",
|
||||
"undo": "撤销",
|
||||
"redo": "重做",
|
||||
"filter": "筛选",
|
||||
"adjust": "调整",
|
||||
"draw": "绘制",
|
||||
"sticker": "贴纸",
|
||||
"brushColor": "笔刷颜色",
|
||||
"font": "字体",
|
||||
"background": "背景",
|
||||
"align": "对齐",
|
||||
"addedToAlbums": "{count, plural, =1{已成功添加到 1 个相册} other{已成功添加到 {count} 个相册}}",
|
||||
"@addedToAlbums": {
|
||||
"description": "Message shown when items are added to albums",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import 'package:photos/services/sync/remote_sync_service.dart';
|
||||
import "package:photos/services/sync/sync_service.dart";
|
||||
import "package:photos/services/video_preview_service.dart";
|
||||
import "package:photos/services/wake_lock_service.dart";
|
||||
import "package:photos/src/rust/frb_generated.dart";
|
||||
import 'package:photos/ui/tools/app_lock.dart';
|
||||
import 'package:photos/ui/tools/lock_screen.dart';
|
||||
import "package:photos/utils/email_util.dart";
|
||||
@@ -65,6 +66,7 @@ bool _stopHearBeat = false;
|
||||
|
||||
void main() async {
|
||||
debugRepaintRainbowEnabled = false;
|
||||
await RustLib.init();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
MediaKit.ensureInitialized();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ class SetupSRPRequest {
|
||||
final String srpA;
|
||||
final bool isUpdate;
|
||||
|
||||
|
||||
SetupSRPRequest({
|
||||
required this.srpUserID,
|
||||
required this.srpSalt,
|
||||
@@ -82,6 +81,7 @@ class CompleteSRPSetupRequest {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SrpAttributes {
|
||||
final String srpUserID;
|
||||
final String srpSalt;
|
||||
|
||||
@@ -9,14 +9,12 @@ import "package:photos/models/memories/smart_memory.dart";
|
||||
import "package:photos/models/memories/smart_memory_constants.dart";
|
||||
import "package:photos/models/memories/trip_memory.dart";
|
||||
|
||||
const kPersonShowTimeout = Duration(days: 7 * 10);
|
||||
const kPersonAndTypeShowTimeout = Duration(days: 7 * 26);
|
||||
const kClipShowTimeout = Duration(days: 3 * 10);
|
||||
const kTripShowTimeout = Duration(days: 7 * 25);
|
||||
const kPersonShowTimeout = Duration(days: 16 * kMemoriesUpdateFrequencyDays);
|
||||
const kClipShowTimeout = Duration(days: 10 * kMemoriesUpdateFrequencyDays);
|
||||
const kTripShowTimeout = Duration(days: 50 * kMemoriesUpdateFrequencyDays);
|
||||
|
||||
final maxShowTimeout = [
|
||||
kPersonShowTimeout,
|
||||
kPersonAndTypeShowTimeout,
|
||||
kTripShowTimeout,
|
||||
].reduce((value, element) => value > element ? value : element) *
|
||||
3;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Constants for computing smart memories
|
||||
const kMemoriesUpdateFrequency = Duration(days: 3);
|
||||
const kMemoriesUpdateFrequencyDays = 3;
|
||||
const kMemoriesUpdateFrequency = Duration(days: kMemoriesUpdateFrequencyDays);
|
||||
const kMemoriesMargin = Duration(days: 1);
|
||||
const kDayItself = Duration(days: 1);
|
||||
|
||||
@@ -79,8 +79,10 @@ class SelectedFiles extends ChangeNotifier {
|
||||
return false;
|
||||
}
|
||||
|
||||
void clearAll() {
|
||||
Bus.instance.fire(ClearSelectionsEvent());
|
||||
void clearAll({bool fireEvent = true}) {
|
||||
if (fireEvent) {
|
||||
Bus.instance.fire(ClearSelectionsEvent());
|
||||
}
|
||||
lastSelectionOperationFiles.addAll(files);
|
||||
files.clear();
|
||||
notifyListeners();
|
||||
|
||||
32
mobile/apps/photos/lib/models/similar_files.dart
Normal file
32
mobile/apps/photos/lib/models/similar_files.dart
Normal file
@@ -0,0 +1,32 @@
|
||||
import "package:photos/models/file/file.dart";
|
||||
|
||||
class SimilarFiles {
|
||||
final List<EnteFile> files;
|
||||
final Set<int> fileIds;
|
||||
final double furthestDistance;
|
||||
|
||||
SimilarFiles(
|
||||
this.files,
|
||||
this.furthestDistance,
|
||||
) : fileIds = files.map((file) => file.uploadedFileID!).toSet();
|
||||
|
||||
int get totalSize =>
|
||||
files.fold(0, (sum, file) => sum + (file.fileSize ?? 0));
|
||||
|
||||
bool get isEmpty => files.isEmpty;
|
||||
|
||||
int get length => files.length;
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'SimilarFiles(files: $files, size: $totalSize, distance: $furthestDistance)';
|
||||
|
||||
void removeFile(EnteFile file) {
|
||||
files.remove(file);
|
||||
fileIds.remove(file.uploadedFileID);
|
||||
}
|
||||
|
||||
bool containsFile(EnteFile file) {
|
||||
return fileIds.contains(file.uploadedFileID);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'dart:async';
|
||||
import "dart:typed_data" show Float32List;
|
||||
|
||||
import "package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart"
|
||||
show Uint64List;
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/models/ml/vector.dart";
|
||||
import "package:photos/services/machine_learning/ml_constants.dart";
|
||||
@@ -32,6 +35,40 @@ class MLComputer extends SuperIsolate {
|
||||
static final MLComputer instance = MLComputer._privateConstructor();
|
||||
factory MLComputer() => instance;
|
||||
|
||||
Future<(List<Uint64List>, List<Float32List>)> bulkVectorSearch(
|
||||
List<Float32List> clipFloat32,
|
||||
bool exact,
|
||||
) async {
|
||||
try {
|
||||
final result = await runInIsolate(IsolateOperation.bulkVectorSearch, {
|
||||
"clipFloat32": clipFloat32,
|
||||
"exact": exact,
|
||||
});
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Could not run bulk vector search in MLComputer", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<(Uint64List, List<Uint64List>, List<Float32List>)>
|
||||
bulkVectorSearchWithKeys(
|
||||
Uint64List potentialKeys,
|
||||
bool exact,
|
||||
) async {
|
||||
try {
|
||||
final result =
|
||||
await runInIsolate(IsolateOperation.bulkVectorSearchWithKeys, {
|
||||
"potentialKeys": potentialKeys,
|
||||
"exact": exact,
|
||||
});
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Could not run bulk vector search in MLComputer", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<double>> runClipText(String query) async {
|
||||
try {
|
||||
await _ensureLoadedClipTextModel();
|
||||
|
||||
@@ -1 +1 @@
|
||||
const imageEmbeddingsKey = "imageEmbeddings";
|
||||
const imageEmbeddingsKey = "imageEmbeddings";
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
class GeneralFaceMlException implements Exception {
|
||||
final String message;
|
||||
|
||||
@@ -26,4 +25,4 @@ class CouldNotRunFaceDetector implements Exception {}
|
||||
|
||||
class CouldNotWarpAffine implements Exception {}
|
||||
|
||||
class CouldNotRunFaceEmbeddor implements Exception {}
|
||||
class CouldNotRunFaceEmbeddor implements Exception {}
|
||||
|
||||
@@ -3,4 +3,4 @@ class QueryResult {
|
||||
final double score;
|
||||
|
||||
QueryResult(this.id, this.score);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import "dart:async" show Timer, unawaited;
|
||||
import "dart:developer" as dev show log;
|
||||
import "dart:math" show min;
|
||||
import "dart:ui" show Image;
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
@@ -149,11 +147,13 @@ class SemanticSearchService {
|
||||
},
|
||||
);
|
||||
final queryResults = similarityResults[query]!;
|
||||
// print query for top ten scores
|
||||
for (int i = 0; i < min(10, queryResults.length); i++) {
|
||||
final result = queryResults[i];
|
||||
dev.log("Query: $query, Score: ${result.score}, index $i");
|
||||
}
|
||||
// Uncomment if needed for debugging: print query for top ten scores
|
||||
// if (kDebugMode) {
|
||||
// for (int i = 0; i < min(10, queryResults.length); i++) {
|
||||
// final result = queryResults[i];
|
||||
// dev.log("Query: $query, Score: ${result.score}, index $i");
|
||||
// }
|
||||
// }
|
||||
|
||||
final Map<int, double> fileIDToScoreMap = {};
|
||||
for (final result in queryResults) {
|
||||
@@ -265,10 +265,19 @@ class SemanticSearchService {
|
||||
required Map<String, double> minimumSimilarityMap,
|
||||
}) async {
|
||||
final startTime = DateTime.now();
|
||||
// Uncomment if needed for debugging: print query embeddings
|
||||
// if (kDebugMode) {
|
||||
// for (final queryText in textQueryToEmbeddingMap.keys) {
|
||||
// final embedding = textQueryToEmbeddingMap[queryText]!;
|
||||
// dev.log("CLIPTEXT Query: $queryText, embedding: $embedding");
|
||||
// }
|
||||
// }
|
||||
await _cacheClipVectors();
|
||||
final Map<String, List<QueryResult>> queryResults = await MLComputer
|
||||
.instance
|
||||
.computeBulkSimilarities(textQueryToEmbeddingMap, minimumSimilarityMap);
|
||||
final Map<String, List<QueryResult>> queryResults =
|
||||
await MLComputer.instance.computeBulkSimilarities(
|
||||
textQueryToEmbeddingMap,
|
||||
minimumSimilarityMap,
|
||||
);
|
||||
final endTime = DateTime.now();
|
||||
_logger.info(
|
||||
"computingSimilarities took for ${textQueryToEmbeddingMap.length} queries " +
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import "dart:math" show max;
|
||||
|
||||
import "package:flutter/foundation.dart" show kDebugMode;
|
||||
import "package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart"
|
||||
show Uint64List;
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:photos/db/ml/db.dart";
|
||||
import "package:photos/extensions/stop_watch.dart";
|
||||
import 'package:photos/models/file/file.dart';
|
||||
import "package:photos/models/file/file_type.dart";
|
||||
import "package:photos/models/similar_files.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";
|
||||
|
||||
class SimilarImagesService {
|
||||
final _logger = Logger("SimilarImagesService");
|
||||
|
||||
SimilarImagesService._privateConstructor();
|
||||
static final SimilarImagesService instance =
|
||||
SimilarImagesService._privateConstructor();
|
||||
|
||||
/// Returns a list of SimilarFiles, where each SimilarFiles object contains
|
||||
/// a list of files that are perceptually similar
|
||||
Future<List<SimilarFiles>> getSimilarFiles(
|
||||
double distanceThreshold, {
|
||||
bool exact = false,
|
||||
}) async {
|
||||
try {
|
||||
final now = DateTime.now();
|
||||
final List<SimilarFiles> result =
|
||||
await _getSimilarFiles(distanceThreshold, exact);
|
||||
final duration = DateTime.now().difference(now);
|
||||
_logger.info(
|
||||
"Found ${result.length} similar files in ${duration.inSeconds} seconds for threshold $distanceThreshold and exact $exact",
|
||||
);
|
||||
return result;
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to get similar files", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SimilarFiles>> _getSimilarFiles(
|
||||
double distanceThreshold,
|
||||
bool exact,
|
||||
) async {
|
||||
final w = (kDebugMode ? EnteWatch('getSimilarFiles') : null)?..start();
|
||||
final mlDataDB = MLDataDB.instance;
|
||||
_logger.info("Checking migration and filling clip vector DB");
|
||||
await mlDataDB.checkMigrateFillClipVectorDB();
|
||||
w?.log("checkMigrateFillClipVectorDB");
|
||||
|
||||
// Get all files, and all potential embedding IDs, and create a map of fileID to file
|
||||
final allFiles = Set<EnteFile>.from(
|
||||
await SearchService.instance.getAllFilesForSearch(),
|
||||
);
|
||||
final allFileIdsToFile = <int, EnteFile>{};
|
||||
final fileIDs = <int>[];
|
||||
for (final file in allFiles) {
|
||||
if (file.uploadedFileID != null && file.fileType != FileType.video) {
|
||||
allFileIdsToFile[file.uploadedFileID!] = file;
|
||||
fileIDs.add(file.uploadedFileID!);
|
||||
}
|
||||
}
|
||||
final Uint64List potentialKeys = Uint64List.fromList(fileIDs);
|
||||
w?.log("getAllFilesForSearch");
|
||||
|
||||
// Get mapping of fileIDs to corresponding personIDs
|
||||
final fileIDToPersonIDs = <int, Set<String>>{};
|
||||
final dbPersonClusterInfo = await mlDataDB.getPersonToClusterIdToFaceIds();
|
||||
for (final personID in dbPersonClusterInfo.keys) {
|
||||
final clusterInfo = dbPersonClusterInfo[personID]!;
|
||||
for (final faceIDs in clusterInfo.values) {
|
||||
for (final faceID in faceIDs) {
|
||||
final fileID = getFileIdFromFaceId<int>(faceID);
|
||||
if (allFileIdsToFile.containsKey(fileID)) {
|
||||
fileIDToPersonIDs
|
||||
.putIfAbsent(fileID, () => <String>{})
|
||||
.add(personID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
w?.log("getFileIDToPersonIDs");
|
||||
|
||||
// Run bulk vector search
|
||||
final (keys, vectorKeys, distances) =
|
||||
await MLComputer.instance.bulkVectorSearchWithKeys(
|
||||
potentialKeys,
|
||||
exact,
|
||||
);
|
||||
w?.log("bulkSearchVectors");
|
||||
|
||||
// Run through the vector search results and create SimilarFiles objects
|
||||
final alreadyUsedFileIDs = <int>{};
|
||||
final allSimilarFiles = <SimilarFiles>[];
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
final fileID = keys[i].toInt();
|
||||
if (alreadyUsedFileIDs.contains(fileID)) continue;
|
||||
final firstLoopFile = allFileIdsToFile[fileID];
|
||||
if (firstLoopFile == null || firstLoopFile.uploadedFileID == null) {
|
||||
continue;
|
||||
}
|
||||
final otherFileIDs = vectorKeys[i];
|
||||
final distancesToFiles = distances[i];
|
||||
final similarFilesList = <EnteFile>[];
|
||||
final personIDs = fileIDToPersonIDs[fileID] ?? <String>{};
|
||||
double furthestDistance = 0.0;
|
||||
for (int j = 0; j < otherFileIDs.length; j++) {
|
||||
final otherFileID = otherFileIDs[j].toInt();
|
||||
if (otherFileID == fileID) continue;
|
||||
if (alreadyUsedFileIDs.contains(otherFileID)) continue;
|
||||
final distance = distancesToFiles[j];
|
||||
if (distance > distanceThreshold) break;
|
||||
final otherFile = allFileIdsToFile[otherFileID];
|
||||
if (otherFile == null || otherFile.uploadedFileID == null) {
|
||||
continue;
|
||||
}
|
||||
final otherPersonIDs = fileIDToPersonIDs[otherFileID] ?? <String>{};
|
||||
if (!setsAreEqual(personIDs, otherPersonIDs)) continue;
|
||||
similarFilesList.add(otherFile);
|
||||
furthestDistance = max(furthestDistance, distance);
|
||||
}
|
||||
if (similarFilesList.isNotEmpty) {
|
||||
similarFilesList.add(firstLoopFile);
|
||||
for (final file in similarFilesList) {
|
||||
alreadyUsedFileIDs.add(file.uploadedFileID!);
|
||||
}
|
||||
similarFilesList.sort((a, b) {
|
||||
return a.displayName.length.compareTo(b.displayName.length);
|
||||
});
|
||||
final similarFiles = SimilarFiles(
|
||||
similarFilesList,
|
||||
furthestDistance,
|
||||
);
|
||||
allSimilarFiles.add(similarFiles);
|
||||
}
|
||||
}
|
||||
w?.log("going through files");
|
||||
|
||||
return allSimilarFiles;
|
||||
}
|
||||
}
|
||||
|
||||
bool setsAreEqual(Set<String> set1, Set<String> set2) {
|
||||
return set1.length == set2.length && set1.containsAll(set2);
|
||||
}
|
||||
@@ -35,9 +35,9 @@ import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/language_service.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/person/person_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";
|
||||
import "package:photos/utils/text_embeddings_util.dart";
|
||||
|
||||
class MemoriesResult {
|
||||
final List<SmartMemory> memories;
|
||||
@@ -103,24 +103,18 @@ class SmartMemoriesService {
|
||||
'allImageEmbeddings has ${allImageEmbeddings.length} entries $t',
|
||||
);
|
||||
|
||||
const String clipPositiveQuery =
|
||||
'Photo of a precious and nostalgic memory radiating warmth, vibrant energy, or quiet beauty — alive with color, light, or emotion';
|
||||
final clipPositiveTextVector = Vector.fromList(
|
||||
await MLComputer.instance.runClipText(clipPositiveQuery),
|
||||
);
|
||||
final Map<PeopleActivity, Vector> clipPeopleActivityVectors = {};
|
||||
for (final peopleActivity in PeopleActivity.values) {
|
||||
clipPeopleActivityVectors[peopleActivity] ??= Vector.fromList(
|
||||
await MLComputer.instance.runClipText(activityQuery(peopleActivity)),
|
||||
// Load pre-computed text embeddings from assets
|
||||
final textEmbeddings = await loadTextEmbeddingsFromAssets();
|
||||
if (textEmbeddings == null) {
|
||||
_logger.severe('Failed to load pre-computed text embeddings');
|
||||
throw Exception(
|
||||
'Failed to load pre-computed text embeddings',
|
||||
);
|
||||
}
|
||||
final Map<ClipMemoryType, Vector> clipMemoryTypeVectors = {};
|
||||
for (final clipMemoryType in ClipMemoryType.values) {
|
||||
clipMemoryTypeVectors[clipMemoryType] ??= Vector.fromList(
|
||||
await MLComputer.instance.runClipText(clipQuery(clipMemoryType)),
|
||||
);
|
||||
}
|
||||
_logger.info('clipPositiveTextVector and clipPeopleActivityVectors $t');
|
||||
_logger.info('Using pre-computed text embeddings from assets');
|
||||
final clipPositiveTextVector = textEmbeddings.clipPositiveVector;
|
||||
final clipPeopleActivityVectors = textEmbeddings.peopleActivityVectors;
|
||||
final clipMemoryTypeVectors = textEmbeddings.clipMemoryTypeVectors;
|
||||
|
||||
final local = await getLocale();
|
||||
final languageCode = local?.languageCode ?? "en";
|
||||
@@ -422,6 +416,7 @@ class SmartMemoriesService {
|
||||
.map((p) => p.remoteID)
|
||||
.toList();
|
||||
orderedImportantPersonsID.shuffle(Random());
|
||||
final amountOfPersons = orderedImportantPersonsID.length;
|
||||
w?.log('orderedImportantPersonsID setup');
|
||||
|
||||
// Check if the user has assignmed "me"
|
||||
@@ -709,6 +704,14 @@ class SmartMemoriesService {
|
||||
w?.log('relevancy setup');
|
||||
|
||||
// Loop through the people (and memory types) and add based on rotation
|
||||
final shownPersonTimeout = Duration(
|
||||
days: min(
|
||||
kPersonShowTimeout.inDays,
|
||||
max(1, amountOfPersons) * kMemoriesUpdateFrequencyDays,
|
||||
),
|
||||
);
|
||||
final shownPersonAndTypeTimeout =
|
||||
Duration(days: shownPersonTimeout.inDays * 2);
|
||||
peopleRotationLoop:
|
||||
for (final personID in orderedImportantPersonsID) {
|
||||
for (final memory in memoryResults) {
|
||||
@@ -721,11 +724,13 @@ class SmartMemoriesService {
|
||||
final shownDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(shownLog.lastTimeShown);
|
||||
final bool seenPersonRecently =
|
||||
currentTime.difference(shownDate) < kPersonShowTimeout;
|
||||
currentTime.difference(shownDate) < shownPersonTimeout;
|
||||
if (seenPersonRecently) continue peopleRotationLoop;
|
||||
}
|
||||
if (personToMemories[personID] == null) continue peopleRotationLoop;
|
||||
int added = 0;
|
||||
final amountOfMemoryTypesForPerson = personToMemories[personID]!.length;
|
||||
final bool manyMemoryTypes = amountOfMemoryTypesForPerson > 2;
|
||||
potentialMemoryLoop:
|
||||
for (final memoriesForCategory in personToMemories[personID]!.values) {
|
||||
PeopleMemory potentialMemory = memoriesForCategory.first;
|
||||
@@ -748,8 +753,10 @@ class SmartMemoriesService {
|
||||
final shownTypeDate =
|
||||
DateTime.fromMicrosecondsSinceEpoch(shownLog.lastTimeShown);
|
||||
final bool seenPersonTypeRecently =
|
||||
currentTime.difference(shownTypeDate) < kPersonAndTypeShowTimeout;
|
||||
if (seenPersonTypeRecently) continue potentialMemoryLoop;
|
||||
currentTime.difference(shownTypeDate) < shownPersonAndTypeTimeout;
|
||||
if (manyMemoryTypes && seenPersonTypeRecently) {
|
||||
continue potentialMemoryLoop;
|
||||
}
|
||||
}
|
||||
memoryResults.add(potentialMemory);
|
||||
added++;
|
||||
|
||||
12
mobile/apps/photos/lib/src/rust/api/simple.dart
Normal file
12
mobile/apps/photos/lib/src/rust/api/simple.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: require_trailing_commas
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:photos/src/rust/frb_generated.dart';
|
||||
|
||||
String greet({required String name}) =>
|
||||
RustLib.instance.api.crateApiSimpleGreet(name: name);
|
||||
57
mobile/apps/photos/lib/src/rust/api/usearch_api.dart
Normal file
57
mobile/apps/photos/lib/src/rust/api/usearch_api.dart
Normal file
@@ -0,0 +1,57 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: require_trailing_commas
|
||||
|
||||
// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
|
||||
import 'package:photos/src/rust/frb_generated.dart';
|
||||
|
||||
// These functions are ignored because they are not marked as `pub`: `ensure_capacity`, `save_index`
|
||||
|
||||
// Rust type: RustOpaqueMoi<flutter_rust_bridge::for_generated::RustAutoOpaqueInner<VectorDB>>
|
||||
abstract class VectorDb implements RustOpaqueInterface {
|
||||
Future<void> addVector({required BigInt key, required List<double> vector});
|
||||
|
||||
Future<void> bulkAddVectors(
|
||||
{required Uint64List keys, required List<Float32List> vectors});
|
||||
|
||||
Future<List<Float32List>> bulkGetVectors({required Uint64List keys});
|
||||
|
||||
Future<BigInt> bulkRemoveVectors({required Uint64List keys});
|
||||
|
||||
Future<(Uint64List, List<Uint64List>, List<Float32List>)> bulkSearchKeys(
|
||||
{required Uint64List potentialKeys,
|
||||
required BigInt count,
|
||||
required bool exact});
|
||||
|
||||
Future<(List<Uint64List>, List<Float32List>)> bulkSearchVectors(
|
||||
{required List<Float32List> queries,
|
||||
required BigInt count,
|
||||
required bool exact});
|
||||
|
||||
/// Check if a vector with the given key exists in the index.
|
||||
/// `true` if the index contains the vector with the given key, `false` otherwise.
|
||||
Future<bool> containsVector({required BigInt key});
|
||||
|
||||
Future<void> deleteIndex();
|
||||
|
||||
Future<(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)>
|
||||
getIndexStats();
|
||||
|
||||
Future<Float32List> getVector({required BigInt key});
|
||||
|
||||
factory VectorDb({required String filePath, required BigInt dimensions}) =>
|
||||
RustLib.instance.api.crateApiUsearchApiVectorDbNew(
|
||||
filePath: filePath, dimensions: dimensions);
|
||||
|
||||
Future<BigInt> removeVector({required BigInt key});
|
||||
|
||||
Future<void> resetIndex();
|
||||
|
||||
Future<(Uint64List, Float32List)> searchVectors(
|
||||
{required List<double> query,
|
||||
required BigInt count,
|
||||
required bool exact});
|
||||
}
|
||||
1244
mobile/apps/photos/lib/src/rust/frb_generated.dart
Normal file
1244
mobile/apps/photos/lib/src/rust/frb_generated.dart
Normal file
File diff suppressed because it is too large
Load Diff
315
mobile/apps/photos/lib/src/rust/frb_generated.io.dart
Normal file
315
mobile/apps/photos/lib/src/rust/frb_generated.io.dart
Normal file
@@ -0,0 +1,315 @@
|
||||
// This file is automatically generated, so please do not edit it.
|
||||
// @generated by `flutter_rust_bridge`@ 2.11.1.
|
||||
|
||||
// ignore_for_file: require_trailing_commas
|
||||
|
||||
// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' as ffi;
|
||||
|
||||
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart';
|
||||
import 'package:photos/src/rust/api/simple.dart';
|
||||
import 'package:photos/src/rust/api/usearch_api.dart';
|
||||
import 'package:photos/src/rust/frb_generated.dart';
|
||||
|
||||
abstract class RustLibApiImplPlatform extends BaseApiImpl<RustLibWire> {
|
||||
RustLibApiImplPlatform({
|
||||
required super.handler,
|
||||
required super.wire,
|
||||
required super.generalizedFrbRustBinding,
|
||||
required super.portManager,
|
||||
});
|
||||
|
||||
CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_VectorDbPtr => wire
|
||||
._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr;
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
String dco_decode_String(dynamic raw);
|
||||
|
||||
@protected
|
||||
bool dco_decode_bool(dynamic raw);
|
||||
|
||||
@protected
|
||||
double dco_decode_f_32(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Float32List> dco_decode_list_list_prim_f_32_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<Uint64List> dco_decode_list_list_prim_u_64_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
List<double> dco_decode_list_prim_f_32_loose(dynamic raw);
|
||||
|
||||
@protected
|
||||
Float32List dco_decode_list_prim_f_32_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint64List dco_decode_list_prim_u_64_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
Uint8List dco_decode_list_prim_u_8_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
(List<Uint64List>, List<Float32List>)
|
||||
dco_decode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
(
|
||||
Uint64List,
|
||||
List<Uint64List>,
|
||||
List<Float32List>
|
||||
) dco_decode_record_list_prim_u_64_strict_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
dynamic raw);
|
||||
|
||||
@protected
|
||||
(
|
||||
Uint64List,
|
||||
Float32List
|
||||
) dco_decode_record_list_prim_u_64_strict_list_prim_f_32_strict(dynamic raw);
|
||||
|
||||
@protected
|
||||
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)
|
||||
dco_decode_record_usize_usize_usize_usize_usize_usize_usize(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_u_64(dynamic raw);
|
||||
|
||||
@protected
|
||||
int dco_decode_u_8(dynamic raw);
|
||||
|
||||
@protected
|
||||
void dco_decode_unit(dynamic raw);
|
||||
|
||||
@protected
|
||||
BigInt dco_decode_usize(dynamic raw);
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
VectorDb
|
||||
sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
String sse_decode_String(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
bool sse_decode_bool(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
double sse_decode_f_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Float32List> sse_decode_list_list_prim_f_32_strict(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<Uint64List> sse_decode_list_list_prim_u_64_strict(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
List<double> sse_decode_list_prim_f_32_loose(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Float32List sse_decode_list_prim_f_32_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Uint64List sse_decode_list_prim_u_64_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(List<Uint64List>, List<Float32List>)
|
||||
sse_decode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(
|
||||
Uint64List,
|
||||
List<Uint64List>,
|
||||
List<Float32List>
|
||||
) sse_decode_record_list_prim_u_64_strict_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(Uint64List, Float32List)
|
||||
sse_decode_record_list_prim_u_64_strict_list_prim_f_32_strict(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt)
|
||||
sse_decode_record_usize_usize_usize_usize_usize_usize_usize(
|
||||
SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_u_64(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_u_8(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void sse_decode_unit(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
BigInt sse_decode_usize(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
int sse_decode_i_32(SseDeserializer deserializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
VectorDb self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
VectorDb self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
VectorDb self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_String(String self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_bool(bool self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_f_32(double self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_f_32_strict(
|
||||
List<Float32List> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_list_prim_u_64_strict(
|
||||
List<Uint64List> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_f_32_loose(
|
||||
List<double> self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_f_32_strict(
|
||||
Float32List self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_64_strict(
|
||||
Uint64List self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_list_prim_u_8_strict(
|
||||
Uint8List self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
(List<Uint64List>, List<Float32List>) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void
|
||||
sse_encode_record_list_prim_u_64_strict_list_list_prim_u_64_strict_list_list_prim_f_32_strict(
|
||||
(Uint64List, List<Uint64List>, List<Float32List>) self,
|
||||
SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_list_prim_u_64_strict_list_prim_f_32_strict(
|
||||
(Uint64List, Float32List) self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_record_usize_usize_usize_usize_usize_usize_usize(
|
||||
(BigInt, BigInt, BigInt, BigInt, BigInt, BigInt, BigInt) self,
|
||||
SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_64(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_u_8(int self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_unit(void self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_usize(BigInt self, SseSerializer serializer);
|
||||
|
||||
@protected
|
||||
void sse_encode_i_32(int self, SseSerializer serializer);
|
||||
}
|
||||
|
||||
// Section: wire_class
|
||||
|
||||
class RustLibWire implements BaseWire {
|
||||
factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) =>
|
||||
RustLibWire(lib.ffiDynamicLibrary);
|
||||
|
||||
/// Holds the symbol lookup function.
|
||||
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
|
||||
_lookup;
|
||||
|
||||
/// The symbols are looked up in [dynamicLibrary].
|
||||
RustLibWire(ffi.DynamicLibrary dynamicLibrary)
|
||||
: _lookup = dynamicLibrary.lookup;
|
||||
|
||||
void
|
||||
rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_photos_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB');
|
||||
late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB =
|
||||
_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
|
||||
void
|
||||
rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
ffi.Pointer<ffi.Void> ptr,
|
||||
) {
|
||||
return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB(
|
||||
ptr,
|
||||
);
|
||||
}
|
||||
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr =
|
||||
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Void>)>>(
|
||||
'frbgen_photos_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB');
|
||||
late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDB =
|
||||
_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerVectorDBPtr
|
||||
.asFunction<void Function(ffi.Pointer<ffi.Void>)>();
|
||||
}
|
||||
@@ -18,8 +18,9 @@ class BottomOfTitleBarWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: showCloseButton ? MainAxisAlignment.spaceBetween :
|
||||
MainAxisAlignment.start,
|
||||
mainAxisAlignment: showCloseButton
|
||||
? MainAxisAlignment.spaceBetween
|
||||
: MainAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
|
||||
@@ -100,7 +100,7 @@ class ExtentsPageView extends StatefulWidget {
|
||||
int? itemCount,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.openDrawer,
|
||||
}) : childrenDelegate = SliverChildBuilderDelegate(
|
||||
}) : childrenDelegate = SliverChildBuilderDelegate(
|
||||
itemBuilder,
|
||||
childCount: itemCount,
|
||||
addAutomaticKeepAlives: false,
|
||||
@@ -198,7 +198,7 @@ class ExtentsPageView extends StatefulWidget {
|
||||
required this.childrenDelegate,
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.openDrawer,
|
||||
}) : extents = 0;
|
||||
}) : extents = 0;
|
||||
|
||||
/// The number of pages to build off screen.
|
||||
///
|
||||
|
||||
@@ -9,8 +9,7 @@ import "package:photos/utils/navigation_util.dart";
|
||||
class HeaderErrorWidget extends StatelessWidget {
|
||||
final Error? _error;
|
||||
|
||||
const HeaderErrorWidget({super.key, required Error? error})
|
||||
: _error = error;
|
||||
const HeaderErrorWidget({super.key, required Error? error}) : _error = error;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user