Merge branch 'main' into placeholder_for_link

This commit is contained in:
Aman Raj Singh Mourya 2024-05-27 14:39:37 +05:30
commit acf7b3a865
503 changed files with 22562 additions and 6275 deletions

View file

@ -4,11 +4,12 @@ labels: ["triage"]
body: body:
- type: markdown - type: markdown
attributes: attributes:
value: > value: |
Before opening a new issue, please ensure you are on the latest Before opening a new bug report, please ensure
version (it might've already been fixed), and that you've searched 1. you are on the latest version (it might've already been fixed),
for existing issues (please add you observations as a comment 2. you've searched for existing issues (please add your observations as a comment there instead of creating a duplicate).
there instead of creating a duplicate).
If you are self hosting, please create a community [Q&A](https://github.com/ente-io/ente/discussions/categories/q-a) instead.
- type: textarea - type: textarea
attributes: attributes:
label: Description label: Description
@ -16,7 +17,8 @@ body:
Please describe the bug. If possible, also include the steps to Please describe the bug. If possible, also include the steps to
reproduce the behaviour, and the expected behaviour (sometimes reproduce the behaviour, and the expected behaviour (sometimes
bugs are just expectation mismatches, in which case this would be bugs are just expectation mismatches, in which case this would be
a good fit for Discussions). a good fit for [feature
requests](https://github.com/ente-io/ente/discussions/categories/feature-requests)).
validations: validations:
required: true required: true
- type: input - type: input

View file

@ -4,7 +4,7 @@ on:
workflow_dispatch: # Allow manually running the action workflow_dispatch: # Allow manually running the action
env: env:
FLUTTER_VERSION: "3.19.3" FLUTTER_VERSION: "3.22.0"
jobs: jobs:
build: build:

View file

@ -9,7 +9,7 @@ on:
- ".github/workflows/mobile-lint.yml" - ".github/workflows/mobile-lint.yml"
env: env:
FLUTTER_VERSION: "3.19.5" FLUTTER_VERSION: "3.22.0"
jobs: jobs:
lint: lint:

6
.gitignore vendored
View file

@ -1,8 +1,6 @@
# Let folks use their custom .vscode settings # Let folks use their custom editor settings
.vscode .vscode
.idea
# macOS # macOS
.DS_Store .DS_Store
.idea
.ente.authenticator.db
.ente.offline_authenticator.db

View file

@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<application android:name="${applicationName}" <application android:name="${applicationName}"
android:label="auth" android:label="Auth"
android:icon="@mipmap/launcher_icon" android:icon="@mipmap/launcher_icon"
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 801 KiB

View file

@ -20,7 +20,7 @@
<string>es</string> <string>es</string>
</array> </array>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>auth</string> <string>Auth</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>

View file

@ -189,7 +189,7 @@ class _AppState extends State<App> with WindowListener, TrayListener {
windowManager.show(); windowManager.show();
break; break;
case 'exit_app': case 'exit_app':
windowManager.close(); windowManager.destroy();
break; break;
} }
} }

View file

@ -20,6 +20,8 @@
"codeIssuerHint": "発行者", "codeIssuerHint": "発行者",
"codeSecretKeyHint": "秘密鍵", "codeSecretKeyHint": "秘密鍵",
"codeAccountHint": "アカウント (you@domain.com)", "codeAccountHint": "アカウント (you@domain.com)",
"codeTagHint": "タグ",
"accountKeyType": "鍵の種類",
"sessionExpired": "セッションが失効しました", "sessionExpired": "セッションが失効しました",
"@sessionExpired": { "@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired" "description": "Title of the dialog when the users current session is invalid/expired"
@ -77,6 +79,7 @@
"data": "データ", "data": "データ",
"importCodes": "コードをインポート", "importCodes": "コードをインポート",
"importTypePlainText": "プレーンテキスト", "importTypePlainText": "プレーンテキスト",
"importTypeEnteEncrypted": "Ente 暗号化されたエクスポート",
"passwordForDecryptingExport": "復号化用パスワード", "passwordForDecryptingExport": "復号化用パスワード",
"passwordEmptyError": "パスワードは空欄にできません", "passwordEmptyError": "パスワードは空欄にできません",
"importFromApp": "{appName} からコードをインポート", "importFromApp": "{appName} からコードをインポート",
@ -121,6 +124,7 @@
"suggestFeatures": "機能を提案", "suggestFeatures": "機能を提案",
"faq": "FAQ", "faq": "FAQ",
"faq_q_1": "Authはどのくらい安全ですか", "faq_q_1": "Authはどのくらい安全ですか",
"faq_a_1": "Ente Authでバックアップされたコードはすべてエンドツーエンドで暗号化されて保存されます。つまり、コードにアクセスできるのはあなただけです。当社のアプリはオープンソースであり、暗号化技術は外部監査を受けています。",
"faq_q_2": "パソコンから私のコードにアクセスできますか?", "faq_q_2": "パソコンから私のコードにアクセスできますか?",
"faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。", "faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。",
"faq_q_3": "コードを削除するにはどうすればいいですか?", "faq_q_3": "コードを削除するにはどうすればいいですか?",
@ -154,6 +158,7 @@
} }
} }
}, },
"invalidQRCode": "QRコードが無効です",
"noRecoveryKeyTitle": "回復キーがありませんか?", "noRecoveryKeyTitle": "回復キーがありませんか?",
"enterEmailHint": "メールアドレスを入力してください", "enterEmailHint": "メールアドレスを入力してください",
"invalidEmailTitle": "メールアドレスが無効です", "invalidEmailTitle": "メールアドレスが無効です",
@ -347,6 +352,7 @@
"deleteCodeAuthMessage": "コードを削除するためには認証が必要です", "deleteCodeAuthMessage": "コードを削除するためには認証が必要です",
"showQRAuthMessage": "QR コードを表示するためには認証が必要です", "showQRAuthMessage": "QR コードを表示するためには認証が必要です",
"confirmAccountDeleteTitle": "アカウントの削除に同意", "confirmAccountDeleteTitle": "アカウントの削除に同意",
"confirmAccountDeleteMessage": "このアカウントは他のEnteアプリも使用している場合はそれらにも紐づけされています。\nすべてのEnteアプリでアップロードされたデータは削除され、アカウントは完全に削除されます。",
"androidBiometricHint": "本人を確認する", "androidBiometricHint": "本人を確認する",
"@androidBiometricHint": { "@androidBiometricHint": {
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
@ -417,5 +423,18 @@
"invalidEndpoint": "無効なエンドポイントです", "invalidEndpoint": "無効なエンドポイントです",
"invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。", "invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。",
"endpointUpdatedMessage": "エンドポイントの更新に成功しました", "endpointUpdatedMessage": "エンドポイントの更新に成功しました",
"customEndpoint": "{endpoint} に接続しました" "customEndpoint": "{endpoint} に接続しました",
"pinText": "固定",
"unpinText": "固定を解除",
"pinnedCodeMessage": "{code} を固定しました",
"unpinnedCodeMessage": "{code} の固定が解除されました",
"tags": "タグ",
"createNewTag": "新しいタグの作成",
"tag": "タグ",
"create": "作成",
"editTag": "タグの編集",
"deleteTagTitle": "タグを削除しますか?",
"deleteTagMessage": "このタグを削除してもよろしいですか?この操作は取り消しできません。",
"somethingWentWrongParsingCode": "{x} のコードを解析できませんでした。",
"updateNotAvailable": "アップデートは利用できません"
} }

View file

@ -20,6 +20,8 @@
"codeIssuerHint": "Emissor", "codeIssuerHint": "Emissor",
"codeSecretKeyHint": "Chave secreta", "codeSecretKeyHint": "Chave secreta",
"codeAccountHint": "Conta (voce@dominio.com)", "codeAccountHint": "Conta (voce@dominio.com)",
"codeTagHint": "Etiqueta",
"accountKeyType": "Tipo de chave",
"sessionExpired": "Sessão expirada", "sessionExpired": "Sessão expirada",
"@sessionExpired": { "@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired" "description": "Title of the dialog when the users current session is invalid/expired"
@ -156,6 +158,7 @@
} }
} }
}, },
"invalidQRCode": "QR Code inválido",
"noRecoveryKeyTitle": "Sem chave de recuperação?", "noRecoveryKeyTitle": "Sem chave de recuperação?",
"enterEmailHint": "Insira o seu endereço de e-mail", "enterEmailHint": "Insira o seu endereço de e-mail",
"invalidEmailTitle": "Endereço de e-mail inválido", "invalidEmailTitle": "Endereço de e-mail inválido",
@ -420,5 +423,16 @@
"invalidEndpoint": "Endpoint inválido", "invalidEndpoint": "Endpoint inválido",
"invalidEndpointMessage": "Desculpe, o endpoint que você inseriu é inválido. Por favor, insira um endpoint válido e tente novamente.", "invalidEndpointMessage": "Desculpe, o endpoint que você inseriu é inválido. Por favor, insira um endpoint válido e tente novamente.",
"endpointUpdatedMessage": "Endpoint atualizado com sucesso", "endpointUpdatedMessage": "Endpoint atualizado com sucesso",
"customEndpoint": "Conectado a {endpoint}" "customEndpoint": "Conectado a {endpoint}",
"pinText": "Fixar",
"pinnedCodeMessage": "{code} foi fixado",
"tags": "Etiquetas",
"createNewTag": "Criar etiqueta",
"tag": "Etiqueta",
"create": "Criar",
"editTag": "Editar etiqueta",
"deleteTagTitle": "Excluir etiqueta?",
"deleteTagMessage": "Tem certeza de que deseja excluir esta etiqueta? Essa ação é irreversível.",
"somethingWentWrongParsingCode": "Não foi possível analisar os códigos {x}.",
"updateNotAvailable": "Atualização não está disponível"
} }

View file

@ -20,6 +20,8 @@
"codeIssuerHint": "发行人", "codeIssuerHint": "发行人",
"codeSecretKeyHint": "私钥", "codeSecretKeyHint": "私钥",
"codeAccountHint": "账户 (you@domain.com)", "codeAccountHint": "账户 (you@domain.com)",
"codeTagHint": "标签",
"accountKeyType": "密钥类型",
"sessionExpired": "会话已过期", "sessionExpired": "会话已过期",
"@sessionExpired": { "@sessionExpired": {
"description": "Title of the dialog when the users current session is invalid/expired" "description": "Title of the dialog when the users current session is invalid/expired"
@ -156,6 +158,7 @@
} }
} }
}, },
"invalidQRCode": "二维码无效",
"noRecoveryKeyTitle": "没有恢复密钥吗?", "noRecoveryKeyTitle": "没有恢复密钥吗?",
"enterEmailHint": "请输入您的电子邮件地址", "enterEmailHint": "请输入您的电子邮件地址",
"invalidEmailTitle": "无效的电子邮件地址", "invalidEmailTitle": "无效的电子邮件地址",
@ -420,5 +423,18 @@
"invalidEndpoint": "端点无效", "invalidEndpoint": "端点无效",
"invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。", "invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。",
"endpointUpdatedMessage": "端点更新成功", "endpointUpdatedMessage": "端点更新成功",
"customEndpoint": "已连接至 {endpoint}" "customEndpoint": "已连接至 {endpoint}",
"pinText": "置顶",
"unpinText": "取消置顶",
"pinnedCodeMessage": "{code} 已被置顶",
"unpinnedCodeMessage": "{code} 已被取消置顶",
"tags": "标签",
"createNewTag": "创建新标签",
"tag": "标签",
"create": "创建",
"editTag": "编辑标签",
"deleteTagTitle": "要删除标签吗?",
"deleteTagMessage": "您确定要删除此标签吗?此操作是不可逆的。",
"somethingWentWrongParsingCode": "我们无法解析 {x} 代码。",
"updateNotAvailable": "更新不可用"
} }

View file

@ -125,10 +125,10 @@ class Code {
final issuer = _getIssuer(uri); final issuer = _getIssuer(uri);
try { try {
return Code( final code = Code(
_getAccount(uri), _getAccount(uri),
issuer, issuer,
_getDigits(uri, issuer), _getDigits(uri),
_getPeriod(uri), _getPeriod(uri),
getSanitizedSecret(uri.queryParameters['secret']!), getSanitizedSecret(uri.queryParameters['secret']!),
_getAlgorithm(uri), _getAlgorithm(uri),
@ -137,6 +137,7 @@ class Code {
rawData, rawData,
display: CodeDisplay.fromUri(uri) ?? CodeDisplay(), display: CodeDisplay.fromUri(uri) ?? CodeDisplay(),
); );
return code;
} catch (e) { } catch (e) {
// if account name contains # without encoding, // if account name contains # without encoding,
// rest of the url are treated as url fragment // rest of the url are treated as url fragment
@ -174,12 +175,11 @@ class Code {
} }
String toOTPAuthUrlFormat() { String toOTPAuthUrlFormat() {
final uri = Uri.parse(rawData); final uri = Uri.parse(rawData.replaceAll("#", '%23'));
final query = {...uri.queryParameters}; final query = {...uri.queryParameters};
query["codeDisplay"] = jsonEncode(display.toJson()); query["codeDisplay"] = jsonEncode(display.toJson());
final newUri = uri.replace(queryParameters: query); final newUri = uri.replace(queryParameters: query);
return jsonEncode(newUri.toString()); return jsonEncode(newUri.toString());
} }
@ -201,11 +201,11 @@ class Code {
} }
} }
static int _getDigits(Uri uri, String issuer) { static int _getDigits(Uri uri) {
try { try {
return int.parse(uri.queryParameters['digits']!); return int.parse(uri.queryParameters['digits']!);
} catch (e) { } catch (e) {
if (issuer.toLowerCase() == "steam") { if (uri.host == "steam") {
return steamDigits; return steamDigits;
} }
return defaultDigits; return defaultDigits;

View file

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
/// Used to store the display settings of a code. /// Used to store the display settings of a code.
class CodeDisplay { class CodeDisplay {
@ -54,13 +55,34 @@ class CodeDisplay {
); );
} }
static CodeDisplay? fromUri(Uri uri) { /// Converts the [CodeDisplay] to a json object.
/// When [safeParsing] is true, the json will be parsed safely.
/// If we fail to parse the json, we will return an empty [CodeDisplay].
static CodeDisplay? fromUri(Uri uri, {bool safeParsing = false}) {
if (!uri.queryParameters.containsKey("codeDisplay")) return null; if (!uri.queryParameters.containsKey("codeDisplay")) return null;
final String codeDisplay = final String codeDisplay =
uri.queryParameters['codeDisplay']!.replaceAll('%2C', ','); uri.queryParameters['codeDisplay']!.replaceAll('%2C', ',');
final decodedDisplay = jsonDecode(codeDisplay); return _parseCodeDisplayJson(codeDisplay, safeParsing);
}
return CodeDisplay.fromJson(decodedDisplay); static CodeDisplay _parseCodeDisplayJson(String json, bool safeParsing) {
try {
final decodedDisplay = jsonDecode(json);
return CodeDisplay.fromJson(decodedDisplay);
} catch (e, s) {
Logger("CodeDisplay")
.severe("Could not parse code display from json", e, s);
// (ng/prateek) Handle the case where we have fragment in the rawDataUrl
if (!json.endsWith("}") && json.contains("}#")) {
Logger("CodeDisplay").warning("ignoring code display as it's invalid");
return CodeDisplay();
}
if (safeParsing) {
return CodeDisplay();
} else {
rethrow;
}
}
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {

View file

@ -240,7 +240,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
final account = _accountController.text.trim(); final account = _accountController.text.trim();
final issuer = _issuerController.text.trim(); final issuer = _issuerController.text.trim();
final secret = _secretController.text.trim().replaceAll(' ', ''); final secret = _secretController.text.trim().replaceAll(' ', '');
final isStreamCode = issuer.toLowerCase() == "steam"; final isStreamCode = issuer.toLowerCase() == "steam" || issuer.toLowerCase().contains('steampowered.com');
if (widget.code != null && widget.code!.secret != secret) { if (widget.code != null && widget.code!.secret != secret) {
ButtonResult? result = await showChoiceActionSheet( ButtonResult? result = await showChoiceActionSheet(
context, context,

View file

@ -41,9 +41,9 @@ class CodeStore {
} else { } else {
code = Code.fromExportJson(decodeJson); code = Code.fromExportJson(decodeJson);
} }
} catch (e) { } catch (e, s) {
code = Code.withError(e, entity.rawData); code = Code.withError(e, entity.rawData);
_logger.severe("Could not parse code", code.err); _logger.severe("Could not parse code", e, s);
} }
code.generatedID = entity.generatedID; code.generatedID = entity.generatedID;
code.hasSynced = entity.hasSynced; code.hasSynced = entity.hasSynced;

View file

@ -48,7 +48,6 @@ class _CodeWidgetState extends State<CodeWidget> {
late bool _shouldShowLargeIcon; late bool _shouldShowLargeIcon;
late bool _hideCode; late bool _hideCode;
bool isMaskingEnabled = false; bool isMaskingEnabled = false;
late final colorScheme = getEnteColorScheme(context);
@override @override
void initState() { void initState() {
@ -78,6 +77,7 @@ class _CodeWidgetState extends State<CodeWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
if (isMaskingEnabled != PreferenceService.instance.shouldHideCodes()) { if (isMaskingEnabled != PreferenceService.instance.shouldHideCodes()) {
isMaskingEnabled = PreferenceService.instance.shouldHideCodes(); isMaskingEnabled = PreferenceService.instance.shouldHideCodes();
_hideCode = isMaskingEnabled; _hideCode = isMaskingEnabled;
@ -91,6 +91,100 @@ class _CodeWidgetState extends State<CodeWidget> {
_isInitialized = true; _isInitialized = true;
} }
final l10n = context.l10n; final l10n = context.l10n;
Widget getCardContents(AppLocalizations l10n) {
return Stack(
children: [
if (widget.code.isPinned)
Align(
alignment: Alignment.topRight,
child: CustomPaint(
painter: PinBgPainter(
color: colorScheme.pinnedBgColor,
),
size: const Size(39, 39),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.code.type.isTOTPCompatible)
CodeTimerProgress(
period: widget.code.period,
),
const SizedBox(height: 16),
Row(
children: [
_shouldShowLargeIcon ? _getIcon() : const SizedBox.shrink(),
Expanded(
child: Column(
children: [
_getTopRow(),
const SizedBox(height: 4),
_getBottomRow(l10n),
],
),
),
],
),
const SizedBox(
height: 20,
),
],
),
if (widget.code.isPinned) ...[
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(right: 6, top: 6),
child: SvgPicture.asset("assets/svg/pin-card.svg"),
),
),
],
],
);
}
Widget clippedCard(AppLocalizations l10n) {
return Container(
height: 132,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
boxShadow:
widget.code.isPinned ? colorScheme.pinnedCardBoxShadow : [],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.transparent,
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onTap: () {
_copyCurrentOTPToClipboard();
},
onDoubleTap: isMaskingEnabled
? () {
setState(
() {
_hideCode = !_hideCode;
},
);
}
: null,
onLongPress: () {
_copyCurrentOTPToClipboard();
},
child: getCardContents(l10n),
),
),
),
);
}
return Container( return Container(
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8), margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
child: Builder( child: Builder(
@ -126,7 +220,7 @@ class _CodeWidgetState extends State<CodeWidget> {
], ],
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
), ),
child: _clippedCard(l10n), child: clippedCard(l10n),
); );
} }
@ -216,7 +310,7 @@ class _CodeWidgetState extends State<CodeWidget> {
], ],
), ),
child: Builder( child: Builder(
builder: (context) => _clippedCard(l10n), builder: (context) => clippedCard(l10n),
), ),
); );
}, },
@ -224,98 +318,6 @@ class _CodeWidgetState extends State<CodeWidget> {
); );
} }
Widget _clippedCard(AppLocalizations l10n) {
return Container(
height: 132,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
boxShadow: widget.code.isPinned ? colorScheme.pinnedCardBoxShadow : [],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Material(
color: Colors.transparent,
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onTap: () {
_copyCurrentOTPToClipboard();
},
onDoubleTap: isMaskingEnabled
? () {
setState(
() {
_hideCode = !_hideCode;
},
);
}
: null,
onLongPress: () {
_copyCurrentOTPToClipboard();
},
child: _getCardContents(l10n),
),
),
),
);
}
Widget _getCardContents(AppLocalizations l10n) {
return Stack(
children: [
if (widget.code.isPinned)
Align(
alignment: Alignment.topRight,
child: CustomPaint(
painter: PinBgPainter(
color: colorScheme.pinnedBgColor,
),
size: const Size(39, 39),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.code.type == Type.totp)
CodeTimerProgress(
period: widget.code.period,
),
const SizedBox(height: 16),
Row(
children: [
_shouldShowLargeIcon ? _getIcon() : const SizedBox.shrink(),
Expanded(
child: Column(
children: [
_getTopRow(),
const SizedBox(height: 4),
_getBottomRow(l10n),
],
),
),
],
),
const SizedBox(
height: 20,
),
],
),
if (widget.code.isPinned) ...[
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.only(right: 6, top: 6),
child: SvgPicture.asset("assets/svg/pin-card.svg"),
),
),
],
],
);
}
Widget _getBottomRow(AppLocalizations l10n) { Widget _getBottomRow(AppLocalizations l10n) {
return Container( return Container(
padding: const EdgeInsets.only(left: 16, right: 16), padding: const EdgeInsets.only(left: 16, right: 16),
@ -585,7 +587,7 @@ class _CodeWidgetState extends State<CodeWidget> {
String _getFormattedCode(String code) { String _getFormattedCode(String code) {
if (_hideCode) { if (_hideCode) {
// replace all digits with // replace all digits with
code = code.replaceAll(RegExp(r'\d'), ''); code = code.replaceAll(RegExp(r'\S'), '');
} }
if (code.length == 6) { if (code.length == 6) {
return "${code.substring(0, 3)} ${code.substring(3, 6)}"; return "${code.substring(0, 3)} ${code.substring(3, 6)}";

View file

@ -1,8 +1,12 @@
import 'package:ente_auth/models/code.dart'; import 'package:ente_auth/models/code.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:otp/otp.dart' as otp; import 'package:otp/otp.dart' as otp;
import 'package:steam_totp/steam_totp.dart';
String getOTP(Code code) { String getOTP(Code code) {
if (code.type == Type.steam) {
return _getSteamCode(code);
}
if (code.type == Type.hotp) { if (code.type == Type.hotp) {
return _getHOTPCode(code); return _getHOTPCode(code);
} }
@ -26,7 +30,18 @@ String _getHOTPCode(Code code) {
); );
} }
String _getSteamCode(Code code, [bool isNext = false]) {
final SteamTOTP steamtotp = SteamTOTP(secret: code.secret);
return steamtotp.generate(
DateTime.now().millisecondsSinceEpoch ~/ 1000 + (isNext ? code.period : 0),
);
}
String getNextTotp(Code code) { String getNextTotp(Code code) {
if (code.type == Type.steam) {
return _getSteamCode(code, true);
}
return otp.OTP.generateTOTPCodeString( return otp.OTP.generateTOTPCodeString(
getSanitizedSecret(code.secret), getSanitizedSecret(code.secret),
DateTime.now().millisecondsSinceEpoch + code.period * 1000, DateTime.now().millisecondsSinceEpoch + code.period * 1000,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -745,6 +745,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
hashlib:
dependency: transitive
description:
name: hashlib
sha256: "67e640e19cc33070113acab3125cd48ebe480a0300e15554dec089b8878a729f"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
hashlib_codecs:
dependency: transitive
description:
name: hashlib_codecs
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
hex: hex:
dependency: transitive dependency: transitive
description: description:
@ -1439,6 +1455,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.1" version: "1.11.1"
steam_totp:
dependency: "direct main"
description:
name: steam_totp
sha256: "3c09143c983f6bb05bb53e9232f9d40bbcc01c596ba0273c3e6bb246729abfa1"
url: "https://pub.dev"
source: hosted
version: "0.0.1"
step_progress_indicator: step_progress_indicator:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -1,6 +1,6 @@
name: ente_auth name: ente_auth
description: ente two-factor authenticator description: ente two-factor authenticator
version: 3.0.1+301 version: 3.0.4+304
publish_to: none publish_to: none
environment: environment:
@ -94,6 +94,7 @@ dependencies:
sqflite_common_ffi: ^2.3.0+4 sqflite_common_ffi: ^2.3.0+4
sqlite3: ^2.1.0 sqlite3: ^2.1.0
sqlite3_flutter_libs: ^0.5.19+1 sqlite3_flutter_libs: ^0.5.19+1
steam_totp: ^0.0.1
step_progress_indicator: ^1.0.2 step_progress_indicator: ^1.0.2
styled_text: ^8.1.0 styled_text: ^8.1.0
tray_manager: ^0.2.1 tray_manager: ^0.2.1

View file

@ -14,7 +14,7 @@
"build:ci": "yarn build-renderer && tsc", "build:ci": "yarn build-renderer && tsc",
"build:quick": "yarn build-renderer && yarn build-main:quick", "build:quick": "yarn build-renderer && yarn build-main:quick",
"dev": "concurrently --kill-others --success first --names 'main,rndr' \"yarn dev-main\" \"yarn dev-renderer\"", "dev": "concurrently --kill-others --success first --names 'main,rndr' \"yarn dev-main\" \"yarn dev-renderer\"",
"dev-main": "tsc && electron app/main.js", "dev-main": "tsc && electron .",
"dev-renderer": "cd ../web && yarn install && yarn dev:photos", "dev-renderer": "cd ../web && yarn install && yarn dev:photos",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"lint": "yarn prettier --check --log-level warn . && eslint --ext .ts src && yarn tsc", "lint": "yarn prettier --check --log-level warn . && eslint --ext .ts src && yarn tsc",

View file

@ -315,32 +315,18 @@ const setupTrayItem = (mainWindow: BrowserWindow) => {
/** /**
* Older versions of our app used to maintain a cache dir using the main * Older versions of our app used to maintain a cache dir using the main
* process. This has been removed in favor of cache on the web layer. * process. This has been removed in favor of cache on the web layer. Delete the
* old cache dir if it exists.
* *
* Delete the old cache dir if it exists. * Added May 2024, v1.7.0. This migration code can be removed after some time
* * once most people have upgraded to newer versions.
* This will happen in two phases. The cache had three subdirectories:
*
* - Two of them, "thumbs" and "files", will be removed now (v1.7.0, May 2024).
*
* - The third one, "face-crops" will be removed once we finish the face search
* changes. See: [Note: Legacy face crops].
*
* This migration code can be removed after some time once most people have
* upgraded to newer versions.
*/ */
const deleteLegacyDiskCacheDirIfExists = async () => { const deleteLegacyDiskCacheDirIfExists = async () => {
const removeIfExists = async (dirPath: string) => {
if (existsSync(dirPath)) {
log.info(`Removing legacy disk cache from ${dirPath}`);
await fs.rm(dirPath, { recursive: true });
}
};
// [Note: Getting the cache path] // [Note: Getting the cache path]
// //
// The existing code was passing "cache" as a parameter to getPath. // The existing code was passing "cache" as a parameter to getPath.
// //
// However, "cache" is not a valid parameter to getPath. It works! (for // However, "cache" is not a valid parameter to getPath. It works (for
// example, on macOS I get `~/Library/Caches`), but it is intentionally not // example, on macOS I get `~/Library/Caches`), but it is intentionally not
// documented as part of the public API: // documented as part of the public API:
// //
@ -353,8 +339,8 @@ const deleteLegacyDiskCacheDirIfExists = async () => {
// @ts-expect-error "cache" works but is not part of the public API. // @ts-expect-error "cache" works but is not part of the public API.
const cacheDir = path.join(app.getPath("cache"), "ente"); const cacheDir = path.join(app.getPath("cache"), "ente");
if (existsSync(cacheDir)) { if (existsSync(cacheDir)) {
await removeIfExists(path.join(cacheDir, "thumbs")); log.info(`Removing legacy disk cache from ${cacheDir}`);
await removeIfExists(path.join(cacheDir, "files")); await fs.rm(cacheDir, { recursive: true });
} }
}; };

View file

@ -24,7 +24,6 @@ import {
updateOnNextRestart, updateOnNextRestart,
} from "./services/app-update"; } from "./services/app-update";
import { import {
legacyFaceCrop,
openDirectory, openDirectory,
openLogDirectory, openLogDirectory,
selectDirectory, selectDirectory,
@ -43,10 +42,10 @@ import {
import { convertToJPEG, generateImageThumbnail } from "./services/image"; import { convertToJPEG, generateImageThumbnail } from "./services/image";
import { logout } from "./services/logout"; import { logout } from "./services/logout";
import { import {
clipImageEmbedding, computeCLIPImageEmbedding,
clipTextEmbeddingIfAvailable, computeCLIPTextEmbeddingIfAvailable,
} from "./services/ml-clip"; } from "./services/ml-clip";
import { detectFaces, faceEmbeddings } from "./services/ml-face"; import { computeFaceEmbeddings, detectFaces } from "./services/ml-face";
import { encryptionKey, saveEncryptionKey } from "./services/store"; import { encryptionKey, saveEncryptionKey } from "./services/store";
import { import {
clearPendingUploads, clearPendingUploads,
@ -170,24 +169,22 @@ export const attachIPCHandlers = () => {
// - ML // - ML
ipcMain.handle("clipImageEmbedding", (_, jpegImageData: Uint8Array) => ipcMain.handle(
clipImageEmbedding(jpegImageData), "computeCLIPImageEmbedding",
(_, jpegImageData: Uint8Array) =>
computeCLIPImageEmbedding(jpegImageData),
); );
ipcMain.handle("clipTextEmbeddingIfAvailable", (_, text: string) => ipcMain.handle("computeCLIPTextEmbeddingIfAvailable", (_, text: string) =>
clipTextEmbeddingIfAvailable(text), computeCLIPTextEmbeddingIfAvailable(text),
); );
ipcMain.handle("detectFaces", (_, input: Float32Array) => ipcMain.handle("detectFaces", (_, input: Float32Array) =>
detectFaces(input), detectFaces(input),
); );
ipcMain.handle("faceEmbeddings", (_, input: Float32Array) => ipcMain.handle("computeFaceEmbeddings", (_, input: Float32Array) =>
faceEmbeddings(input), computeFaceEmbeddings(input),
);
ipcMain.handle("legacyFaceCrop", (_, faceID: string) =>
legacyFaceCrop(faceID),
); );
// - Upload // - Upload

View file

@ -163,7 +163,7 @@ const checkForUpdatesAndNotify = async (mainWindow: BrowserWindow) => {
}; };
/** /**
* Return the version of the desktop app * Return the version of the desktop app.
* *
* The return value is of the form `v1.2.3`. * The return value is of the form `v1.2.3`.
*/ */

View file

@ -1,7 +1,5 @@
import { shell } from "electron/common"; import { shell } from "electron/common";
import { app, dialog } from "electron/main"; import { app, dialog } from "electron/main";
import { existsSync } from "fs";
import fs from "node:fs/promises";
import path from "node:path"; import path from "node:path";
import { posixPath } from "../utils/electron"; import { posixPath } from "../utils/electron";
@ -53,14 +51,6 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath());
* "userData" directory. This is the **primary** place applications are meant to * "userData" directory. This is the **primary** place applications are meant to
* store user's data, e.g. various configuration files and saved state. * store user's data, e.g. various configuration files and saved state.
* *
* During development, our app name is "Electron", so this'd be, for example,
* `~/Library/Application Support/Electron` if we run using `yarn dev`. For the
* packaged production app, our app name is "ente", so this would be:
*
* - Windows: `%APPDATA%\ente`, e.g. `C:\Users\<username>\AppData\Local\ente`
* - Linux: `~/.config/ente`
* - macOS: `~/Library/Application Support/ente`
*
* Note that Chromium also stores the browser state, e.g. localStorage or disk * Note that Chromium also stores the browser state, e.g. localStorage or disk
* caches, in userData. * caches, in userData.
* *
@ -73,21 +63,7 @@ export const openLogDirectory = () => openDirectory(logDirectoryPath());
* "ente.log", it can be found at: * "ente.log", it can be found at:
* *
* - macOS: ~/Library/Logs/ente/ente.log (production) * - macOS: ~/Library/Logs/ente/ente.log (production)
* - macOS: ~/Library/Logs/Electron/ente.log (dev)
* - Linux: ~/.config/ente/logs/ente.log * - Linux: ~/.config/ente/logs/ente.log
* - Windows: %USERPROFILE%\AppData\Roaming\ente\logs\ente.log * - Windows: %USERPROFILE%\AppData\Roaming\ente\logs\ente.log
*/ */
const logDirectoryPath = () => app.getPath("logs"); const logDirectoryPath = () => app.getPath("logs");
/**
* See: [Note: Legacy face crops]
*/
export const legacyFaceCrop = async (
faceID: string,
): Promise<Uint8Array | undefined> => {
// See: [Note: Getting the cache path]
// @ts-expect-error "cache" works but is not part of the public API.
const cacheDir = path.join(app.getPath("cache"), "ente");
const filePath = path.join(cacheDir, "face-crops", faceID);
return existsSync(filePath) ? await fs.readFile(filePath) : undefined;
};

View file

@ -11,7 +11,7 @@ import * as ort from "onnxruntime-node";
import Tokenizer from "../../thirdparty/clip-bpe-ts/mod"; import Tokenizer from "../../thirdparty/clip-bpe-ts/mod";
import log from "../log"; import log from "../log";
import { writeStream } from "../stream"; import { writeStream } from "../stream";
import { ensure } from "../utils/common"; import { ensure, wait } from "../utils/common";
import { deleteTempFile, makeTempFilePath } from "../utils/temp"; import { deleteTempFile, makeTempFilePath } from "../utils/temp";
import { makeCachedInferenceSession } from "./ml"; import { makeCachedInferenceSession } from "./ml";
@ -20,7 +20,7 @@ const cachedCLIPImageSession = makeCachedInferenceSession(
351468764 /* 335.2 MB */, 351468764 /* 335.2 MB */,
); );
export const clipImageEmbedding = async (jpegImageData: Uint8Array) => { export const computeCLIPImageEmbedding = async (jpegImageData: Uint8Array) => {
const tempFilePath = await makeTempFilePath(); const tempFilePath = await makeTempFilePath();
const imageStream = new Response(jpegImageData.buffer).body; const imageStream = new Response(jpegImageData.buffer).body;
await writeStream(tempFilePath, ensure(imageStream)); await writeStream(tempFilePath, ensure(imageStream));
@ -42,7 +42,7 @@ const clipImageEmbedding_ = async (jpegFilePath: string) => {
const results = await session.run(feeds); const results = await session.run(feeds);
log.debug( log.debug(
() => () =>
`onnx/clip image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, `ONNX/CLIP image embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
); );
/* Need these model specific casts to type the result */ /* Need these model specific casts to type the result */
const imageEmbedding = ensure(results.output).data as Float32Array; const imageEmbedding = ensure(results.output).data as Float32Array;
@ -140,21 +140,23 @@ const getTokenizer = () => {
return _tokenizer; return _tokenizer;
}; };
export const clipTextEmbeddingIfAvailable = async (text: string) => { export const computeCLIPTextEmbeddingIfAvailable = async (text: string) => {
const sessionOrStatus = await Promise.race([ const sessionOrSkip = await Promise.race([
cachedCLIPTextSession(), cachedCLIPTextSession(),
"downloading-model", // Wait for a tick to get the session promise to resolved the first time
// this code runs on each app start (and the model has been downloaded).
wait(0).then(() => 1),
]); ]);
// Don't wait for the download to complete // Don't wait for the download to complete.
if (typeof sessionOrStatus == "string") { if (typeof sessionOrSkip == "number") {
log.info( log.info(
"Ignoring CLIP text embedding request because model download is pending", "Ignoring CLIP text embedding request because model download is pending",
); );
return undefined; return undefined;
} }
const session = sessionOrStatus; const session = sessionOrSkip;
const t1 = Date.now(); const t1 = Date.now();
const tokenizer = getTokenizer(); const tokenizer = getTokenizer();
const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text)); const tokenizedText = Int32Array.from(tokenizer.encodeForCLIP(text));
@ -165,7 +167,7 @@ export const clipTextEmbeddingIfAvailable = async (text: string) => {
const results = await session.run(feeds); const results = await session.run(feeds);
log.debug( log.debug(
() => () =>
`onnx/clip text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`, `ONNX/CLIP text embedding took ${Date.now() - t1} ms (prep: ${t2 - t1} ms, inference: ${Date.now() - t2} ms)`,
); );
const textEmbedding = ensure(results.output).data as Float32Array; const textEmbedding = ensure(results.output).data as Float32Array;
return normalizeEmbedding(textEmbedding); return normalizeEmbedding(textEmbedding);

View file

@ -23,7 +23,7 @@ export const detectFaces = async (input: Float32Array) => {
input: new ort.Tensor("float32", input, [1, 3, 640, 640]), input: new ort.Tensor("float32", input, [1, 3, 640, 640]),
}; };
const results = await session.run(feeds); const results = await session.run(feeds);
log.debug(() => `onnx/yolo face detection took ${Date.now() - t} ms`); log.debug(() => `ONNX/YOLO face detection took ${Date.now() - t} ms`);
return ensure(results.output).data; return ensure(results.output).data;
}; };
@ -32,7 +32,7 @@ const cachedFaceEmbeddingSession = makeCachedInferenceSession(
5286998 /* 5 MB */, 5286998 /* 5 MB */,
); );
export const faceEmbeddings = async (input: Float32Array) => { export const computeFaceEmbeddings = async (input: Float32Array) => {
// Dimension of each face (alias) // Dimension of each face (alias)
const mobileFaceNetFaceSize = 112; const mobileFaceNetFaceSize = 112;
// Smaller alias // Smaller alias
@ -45,7 +45,7 @@ export const faceEmbeddings = async (input: Float32Array) => {
const t = Date.now(); const t = Date.now();
const feeds = { img_inputs: inputTensor }; const feeds = { img_inputs: inputTensor };
const results = await session.run(feeds); const results = await session.run(feeds);
log.debug(() => `onnx/yolo face embedding took ${Date.now() - t} ms`); log.debug(() => `ONNX/MFNT face embedding took ${Date.now() - t} ms`);
/* Need these model specific casts to extract and type the result */ /* Need these model specific casts to extract and type the result */
return (results.embeddings as unknown as Record<string, unknown>) return (results.embeddings as unknown as Record<string, unknown>)
.cpuData as Float32Array; .cpuData as Float32Array;

View file

@ -18,10 +18,7 @@ export const clearStores = () => {
* [Note: Safe storage keys] * [Note: Safe storage keys]
* *
* On macOS, `safeStorage` stores our data under a Keychain entry named * On macOS, `safeStorage` stores our data under a Keychain entry named
* "<app-name> Safe Storage". Which resolves to: * "<app-name> Safe Storage". In our case, "ente Safe Storage".
*
* - Electron Safe Storage (dev)
* - ente Safe Storage (prod)
*/ */
export const saveEncryptionKey = (encryptionKey: string) => { export const saveEncryptionKey = (encryptionKey: string) => {
const encryptedKey = safeStorage.encryptString(encryptionKey); const encryptedKey = safeStorage.encryptString(encryptionKey);

View file

@ -106,7 +106,7 @@ const handleRead = async (path: string) => {
res.headers.set("Content-Length", `${fileSize}`); res.headers.set("Content-Length", `${fileSize}`);
// Add the file's last modified time (as epoch milliseconds). // Add the file's last modified time (as epoch milliseconds).
const mtimeMs = stat.mtimeMs; const mtimeMs = stat.mtime.getTime();
res.headers.set("X-Last-Modified-Ms", `${mtimeMs}`); res.headers.set("X-Last-Modified-Ms", `${mtimeMs}`);
} }
return res; return res;
@ -132,6 +132,13 @@ const handleReadZip = async (zipPath: string, entryName: string) => {
// Close the zip handle when the underlying stream closes. // Close the zip handle when the underlying stream closes.
stream.on("end", () => void zip.close()); stream.on("end", () => void zip.close());
// While it is documented that entry.time is the modification time,
// the units are not mentioned. By seeing the source code, we can
// verify that it is indeed epoch milliseconds. See `parseZipTime`
// in the node-stream-zip source,
// https://github.com/antelle/node-stream-zip/blob/master/node_stream_zip.js
const modifiedMs = entry.time;
return new Response(webReadableStream, { return new Response(webReadableStream, {
headers: { headers: {
// We don't know the exact type, but it doesn't really matter, just // We don't know the exact type, but it doesn't really matter, just
@ -139,12 +146,7 @@ const handleReadZip = async (zipPath: string, entryName: string) => {
// doesn't tinker with it thinking of it as text. // doesn't tinker with it thinking of it as text.
"Content-Type": "application/octet-stream", "Content-Type": "application/octet-stream",
"Content-Length": `${entry.size}`, "Content-Length": `${entry.size}`,
// While it is documented that entry.time is the modification time, "X-Last-Modified-Ms": `${modifiedMs}`,
// the units are not mentioned. By seeing the source code, we can
// verify that it is indeed epoch milliseconds. See `parseZipTime`
// in the node-stream-zip source,
// https://github.com/antelle/node-stream-zip/blob/master/node_stream_zip.js
"X-Last-Modified-Ms": `${entry.time}`,
}, },
}); });
}; };

View file

@ -13,3 +13,12 @@ export const ensure = <T>(v: T | null | undefined): T => {
if (v === undefined) throw new Error("Required value was not found"); if (v === undefined) throw new Error("Required value was not found");
return v; return v;
}; };
/**
* Wait for {@link ms} milliseconds
*
* This function is a promisified `setTimeout`. It returns a promise that
* resolves after {@link ms} milliseconds.
*/
export const wait = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));

View file

@ -55,9 +55,7 @@ export const execAsync = async (command: string | string[]) => {
: command; : command;
const startTime = Date.now(); const startTime = Date.now();
const result = await execAsync_(escapedCommand); const result = await execAsync_(escapedCommand);
log.debug( log.debug(() => `${escapedCommand} (${Date.now() - startTime} ms)`);
() => `${escapedCommand} (${Math.round(Date.now() - startTime)} ms)`,
);
return result; return result;
}; };

View file

@ -65,7 +65,7 @@ const selectDirectory = () => ipcRenderer.invoke("selectDirectory");
const logout = () => { const logout = () => {
watchRemoveListeners(); watchRemoveListeners();
ipcRenderer.send("logout"); return ipcRenderer.invoke("logout");
}; };
const encryptionKey = () => ipcRenderer.invoke("encryptionKey"); const encryptionKey = () => ipcRenderer.invoke("encryptionKey");
@ -153,20 +153,17 @@ const ffmpegExec = (
// - ML // - ML
const clipImageEmbedding = (jpegImageData: Uint8Array) => const computeCLIPImageEmbedding = (jpegImageData: Uint8Array) =>
ipcRenderer.invoke("clipImageEmbedding", jpegImageData); ipcRenderer.invoke("computeCLIPImageEmbedding", jpegImageData);
const clipTextEmbeddingIfAvailable = (text: string) => const computeCLIPTextEmbeddingIfAvailable = (text: string) =>
ipcRenderer.invoke("clipTextEmbeddingIfAvailable", text); ipcRenderer.invoke("computeCLIPTextEmbeddingIfAvailable", text);
const detectFaces = (input: Float32Array) => const detectFaces = (input: Float32Array) =>
ipcRenderer.invoke("detectFaces", input); ipcRenderer.invoke("detectFaces", input);
const faceEmbeddings = (input: Float32Array) => const computeFaceEmbeddings = (input: Float32Array) =>
ipcRenderer.invoke("faceEmbeddings", input); ipcRenderer.invoke("computeFaceEmbeddings", input);
const legacyFaceCrop = (faceID: string) =>
ipcRenderer.invoke("legacyFaceCrop", faceID);
// - Watch // - Watch
@ -340,11 +337,10 @@ contextBridge.exposeInMainWorld("electron", {
// - ML // - ML
clipImageEmbedding, computeCLIPImageEmbedding,
clipTextEmbeddingIfAvailable, computeCLIPTextEmbeddingIfAvailable,
detectFaces, detectFaces,
faceEmbeddings, computeFaceEmbeddings,
legacyFaceCrop,
// - Watch // - Watch

View file

@ -163,6 +163,10 @@ export const sidebar = [
text: "From Authy", text: "From Authy",
link: "/auth/migration-guides/authy/", link: "/auth/migration-guides/authy/",
}, },
{
text: "From Steam",
link: "/auth/migration-guides/steam/",
},
{ {
text: "Exporting your data", text: "Exporting your data",
link: "/auth/migration-guides/export", link: "/auth/migration-guides/export",

View file

@ -7,4 +7,5 @@ description:
# Migrating to/from Ente Auth # Migrating to/from Ente Auth
- [Migrating from Authy](authy/) - [Migrating from Authy](authy/)
- [Importing codes from Steam](steam/)
- [Exporting your data out of Ente Auth](export) - [Exporting your data out of Ente Auth](export)

View file

@ -0,0 +1,79 @@
---
title: Migrating from Steam Authenticator
description: Guide for importing from Steam Authenticator to Ente Auth
---
# Migrating from Steam Authenticator
A guide written by an ente.io lover
> [!WARNING]
>
> Steam Authenticator code is only supported after auth-v3.0.3, check the app's
> version number before migration.
One way to migrate is to
[use this tool by dyc3](https://github.com/dyc3/steamguard-cli/releases/latest)
to simplify the process and skip directly to generating a qr code to Ente
Authenticator.
## Download/Install steamguard-cli
### Windows
1. Download `steamguard.exe` from the [releases page][releases].
2. Place `steamguard.exe` in a folder of your choice. For this example, we will
use `%USERPROFILE%\Desktop`.
3. Open Powershell or Command Prompt. The prompt should be at `%USERPROFILE%`
(eg. `C:\Users\<username>`).
4. Use `cd` to change directory into the folder where you placed
`steamguard.exe`. For this example, it would be `cd Desktop`.
5. You should now be able to run `steamguard.exe` by typing
`.\steamguard.exe --help` and pressing enter.
### Linux
#### Ubuntu/Debian
1. Download the `.deb` from the [releases page][releases].
2. Open a terminal and run this to install it:
```bash
sudo dpkg -i ./steamguard-cli_<version>_amd64.deb
```
#### Other Linux
1. Download `steamguard` from the [releases page][releases]
2. Make it executable, and move `steamguard` to `/usr/local/bin` or any other
directory in your `$PATH`.
```bash
chmod +x ./steamguard
sudo mv ./steamguard /usr/local/bin
```
3. You should now be able to run `steamguard` by typing `steamguard --help` and
pressing enter.
## Login to Steam account
Set up a new account with steamguard-cli
```bash
steamguard setup # set up a new account with steamguard-cli
```
## Generate & importing QR codes
steamguard-cli can then generate a QR code for your 2FA secret.
```bash
steamguard qr # print QR code for the first account in your maFiles
steamguard -u <account name> qr # print QR code for a specific account
```
Open Ente Auth, press the '+' button, select `Scan a QR code`, and scan the qr
code.
You should now have your steam code inside Ente Auth

View file

@ -78,3 +78,23 @@ To summarize:
Set the S3 bucket `endpoint` in `credentials.yaml` to a `yourserverip:3200` or Set the S3 bucket `endpoint` in `credentials.yaml` to a `yourserverip:3200` or
some such IP/hostname that accessible from both where you are running the Ente some such IP/hostname that accessible from both where you are running the Ente
clients (e.g. the mobile app) and also from within the Docker compose cluster. clients (e.g. the mobile app) and also from within the Docker compose cluster.
### 403 Forbidden
If museum (`2`) is able to make a network connection to your S3 bucket (`3`) but
uploads are still failing, it could be a credentials or permissions issue. A
telltale sign of this is that in the museum logs you can see `403 Forbidden`
errors about it not able to find the size of a file even though the
corresponding object exists in the S3 bucket.
To fix these, you should ensure the following:
1. The bucket CORS rules do not allow museum to access these objects.
> For uploading files from the browser, you will need to currently set
> allowedOrigins to "\*", and allow the "X-Auth-Token", "X-Client-Package"
> headers configuration too.
> [Here is an example of a working configuration](https://github.com/ente-io/ente/discussions/1764#discussioncomment-9478204).
2. The credentials are not being picked up (you might be setting the correct
creds, but not in the place where museum picks them from).

View file

@ -46,7 +46,7 @@ You can alternatively install the build from PlayStore or F-Droid.
## 🧑‍💻 Building from source ## 🧑‍💻 Building from source
1. [Install Flutter v3.19.3](https://flutter.dev/docs/get-started/install). 1. [Install Flutter v3.22.0](https://flutter.dev/docs/get-started/install).
2. Pull in all submodules with `git submodule update --init --recursive` 2. Pull in all submodules with `git submodule update --init --recursive`

View file

@ -43,7 +43,7 @@ android {
defaultConfig { defaultConfig {
applicationId "io.ente.photos" applicationId "io.ente.photos"
minSdkVersion 21 minSdkVersion 26
targetSdkVersion 33 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@ -70,6 +70,10 @@ android {
dimension "default" dimension "default"
applicationIdSuffix ".dev" applicationIdSuffix ".dev"
} }
face {
dimension "default"
applicationIdSuffix ".face"
}
playstore { playstore {
dimension "default" dimension "default"
} }

View file

@ -0,0 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="io.ente.photos">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

View file

@ -0,0 +1,4 @@
<resources>
<string name="app_name">ente face</string>
<string name="backup">backup face</string>
</resources>

View file

@ -1,91 +0,0 @@
unknown
person
bicycle
car
motorcycle
airplane
bus
train
truck
boat
traffic light
fire hydrant
unknown
stop sign
parking meter
bench
bird
cat
dog
horse
sheep
cow
elephant
bear
zebra
giraffe
unknown
backpack
umbrella
unknown
unknown
handbag
tie
suitcase
frisbee
skis
snowboard
sports ball
kite
baseball bat
baseball glove
skateboard
surfboard
tennis racket
bottle
unknown
wine glass
cup
fork
knife
spoon
bowl
banana
apple
sandwich
orange
broccoli
carrot
hot dog
pizza
donut
cake
chair
couch
potted plant
bed
unknown
dining table
unknown
unknown
toilet
unknown
tv
laptop
mouse
remote
keyboard
cell phone
microwave
oven
toaster
sink
refrigerator
unknown
book
clock
vase
scissors
teddy bear
hair drier
toothbrush

View file

@ -1,30 +0,0 @@
waterfall
snow
landscape
underwater
architecture
sunset / sunrise
blue sky
cloudy sky
greenery
autumn leaves
portrait
flower
night shot
stage concert
fireworks
candle light
neon lights
indoor
backlight
text documents
qr images
group portrait
computer screens
kids
dog
cat
macro
food
beach
mountain

View file

@ -6,6 +6,8 @@ PODS:
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- dart_ui_isolate (0.0.1):
- Flutter
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- file_saver (0.0.1): - file_saver (0.0.1):
@ -226,6 +228,7 @@ DEPENDENCIES:
- background_fetch (from `.symlinks/plugins/background_fetch/ios`) - background_fetch (from `.symlinks/plugins/background_fetch/ios`)
- battery_info (from `.symlinks/plugins/battery_info/ios`) - battery_info (from `.symlinks/plugins/battery_info/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- dart_ui_isolate (from `.symlinks/plugins/dart_ui_isolate/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
@ -302,6 +305,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/battery_info/ios" :path: ".symlinks/plugins/battery_info/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin" :path: ".symlinks/plugins/connectivity_plus/darwin"
dart_ui_isolate:
:path: ".symlinks/plugins/dart_ui_isolate/ios"
device_info_plus: device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
file_saver: file_saver:
@ -397,6 +402,7 @@ SPEC CHECKSUMS:
background_fetch: 2319bf7e18237b4b269430b7f14d177c0df09c5a background_fetch: 2319bf7e18237b4b269430b7f14d177c0df09c5a
battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
Firebase: 91fefd38712feb9186ea8996af6cbdef41473442 Firebase: 91fefd38712feb9186ea8996af6cbdef41473442
@ -421,7 +427,7 @@ SPEC CHECKSUMS:
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
in_app_purchase_storekit: 0e4b3c2e43ba1e1281f4f46dd71b0593ce529892 in_app_purchase_storekit: 0e4b3c2e43ba1e1281f4f46dd71b0593ce529892
integration_test: 13825b8a9334a850581300559b8839134b124670 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98 local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9

View file

@ -293,6 +293,7 @@
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework", "${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
"${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework", "${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework", "${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/dart_ui_isolate/dart_ui_isolate.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework", "${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework",
"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework", "${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
@ -374,6 +375,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/dart_ui_isolate.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",

View file

@ -65,9 +65,9 @@
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>
<false/> <false/>
<key>FLTEnableImpeller</key> <key>FLTEnableImpeller</key>
<false /> <true />
<key>FLTEnableWideGamut</key> <key>FLTEnableWideGamut</key>
<false/> <true/>
<key>NSFaceIDUsageDescription</key> <key>NSFaceIDUsageDescription</key>
<string>Please allow ente to lock itself with FaceID or TouchID</string> <string>Please allow ente to lock itself with FaceID or TouchID</string>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>

View file

@ -19,6 +19,7 @@ import 'package:photos/db/upload_locks_db.dart';
import "package:photos/events/endpoint_updated_event.dart"; import "package:photos/events/endpoint_updated_event.dart";
import 'package:photos/events/signed_in_event.dart'; import 'package:photos/events/signed_in_event.dart';
import 'package:photos/events/user_logged_out_event.dart'; import 'package:photos/events/user_logged_out_event.dart';
import "package:photos/face/db.dart";
import 'package:photos/models/key_attributes.dart'; import 'package:photos/models/key_attributes.dart';
import 'package:photos/models/key_gen_result.dart'; import 'package:photos/models/key_gen_result.dart';
import 'package:photos/models/private_key_attributes.dart'; import 'package:photos/models/private_key_attributes.dart';
@ -34,10 +35,10 @@ import 'package:photos/services/sync_service.dart';
import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/file_uploader.dart'; import 'package:photos/utils/file_uploader.dart';
import 'package:photos/utils/validator_util.dart'; import 'package:photos/utils/validator_util.dart';
import "package:photos/utils/wakelock_util.dart";
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import "package:tuple/tuple.dart"; import "package:tuple/tuple.dart";
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
class Configuration { class Configuration {
Configuration._privateConstructor(); Configuration._privateConstructor();
@ -187,6 +188,7 @@ class Configuration {
: null; : null;
await CollectionsDB.instance.clearTable(); await CollectionsDB.instance.clearTable();
await MemoriesDB.instance.clearTable(); await MemoriesDB.instance.clearTable();
await FaceMLDataDB.instance.clearTable();
await UploadLocksDB.instance.clearTable(); await UploadLocksDB.instance.clearTable();
await IgnoredFilesService.instance.reset(); await IgnoredFilesService.instance.reset();
@ -583,7 +585,7 @@ class Configuration {
Future<void> setShouldKeepDeviceAwake(bool value) async { Future<void> setShouldKeepDeviceAwake(bool value) async {
await _preferences.setBool(keyShouldKeepDeviceAwake, value); await _preferences.setBool(keyShouldKeepDeviceAwake, value);
await WakelockPlus.toggle(enable: value); await EnteWakeLock.toggle(enable: value);
} }
Future<void> setShouldBackupVideos(bool value) async { Future<void> setShouldBackupVideos(bool value) async {

View file

@ -69,6 +69,8 @@ const galleryGridSpacing = 2.0;
const kSearchSectionLimit = 9; const kSearchSectionLimit = 9;
const maxPickAssetLimit = 50;
const iOSGroupID = "group.io.ente.frame.SlideshowWidget"; const iOSGroupID = "group.io.ente.frame.SlideshowWidget";
const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB' const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
@ -99,6 +101,9 @@ const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' +
'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k='; 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k=';
const localFileServer =
String.fromEnvironment("localFileServer", defaultValue: "");
const uploadTempFilePrefix = "upload_file_"; const uploadTempFilePrefix = "upload_file_";
final tempDirCleanUpInterval = kDebugMode final tempDirCleanUpInterval = kDebugMode
? const Duration(seconds: 30).inMicroseconds ? const Duration(seconds: 30).inMicroseconds

View file

@ -54,7 +54,7 @@ class EmbeddingsDB {
Future<void> clearTable() async { Future<void> clearTable() async {
final db = await _database; final db = await _database;
await db.execute('DELETE * FROM $tableName'); await db.execute('DELETE FROM $tableName');
} }
Future<List<Embedding>> getAll(Model model) async { Future<List<Embedding>> getAll(Model model) async {

View file

@ -9,7 +9,7 @@ extension EntitiesDB on FilesDB {
List<LocalEntityData> data, { List<LocalEntityData> data, {
ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace, ConflictAlgorithm conflictAlgorithm = ConflictAlgorithm.replace,
}) async { }) async {
debugPrint("Inserting missing PathIDToLocalIDMapping"); debugPrint("entitiesDB: upsertEntities ${data.length} entities");
final db = await database; final db = await database;
var batch = db.batch(); var batch = db.batch();
int batchCounter = 0; int batchCounter = 0;
@ -62,4 +62,17 @@ extension EntitiesDB on FilesDB {
return LocalEntityData.fromJson(maps[i]); return LocalEntityData.fromJson(maps[i]);
}); });
} }
Future<LocalEntityData?> getEntity(EntityType type, String id) async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query(
"entities",
where: "type = ? AND id = ?",
whereArgs: [type.typeToString(), id],
);
if (maps.isEmpty) {
return null;
}
return LocalEntityData.fromJson(maps.first);
}
} }

View file

@ -491,6 +491,18 @@ class FilesDB {
return convertToFiles(results)[0]; return convertToFiles(results)[0];
} }
Future<EnteFile?> getAnyUploadedFile(int uploadedID) async {
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
'SELECT * FROM $filesTable WHERE $columnUploadedFileID = ?',
[uploadedID],
);
if (results.isEmpty) {
return null;
}
return convertToFiles(results)[0];
}
Future<Set<int>> getUploadedFileIDs(int collectionID) async { Future<Set<int>> getUploadedFileIDs(int collectionID) async {
final db = await instance.sqliteAsyncDB; final db = await instance.sqliteAsyncDB;
final results = await db.getAll( final results = await db.getAll(
@ -683,6 +695,17 @@ class FilesDB {
return files; return files;
} }
Future<List<EnteFile>> getAllFilesFromCollections(
Iterable<int> collectionID,
) async {
final db = await instance.sqliteAsyncDB;
final String sql =
'SELECT * FROM $filesTable WHERE $columnCollectionID IN (${collectionID.join(',')})';
final results = await db.getAll(sql);
final files = convertToFiles(results);
return files;
}
Future<List<EnteFile>> getNewFilesInCollection( Future<List<EnteFile>> getNewFilesInCollection(
int collectionID, int collectionID,
int addedTime, int addedTime,
@ -1304,6 +1327,23 @@ class FilesDB {
return result; return result;
} }
Future<Map<int, int>> getFileIDToCreationTime() async {
final db = await instance.sqliteAsyncDB;
final rows = await db.getAll(
'''
SELECT $columnUploadedFileID, $columnCreationTime
FROM $filesTable
WHERE
($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1);
''',
);
final result = <int, int>{};
for (final row in rows) {
result[row[columnUploadedFileID] as int] = row[columnCreationTime] as int;
}
return result;
}
// getCollectionFileFirstOrLast returns the first or last uploaded file in // getCollectionFileFirstOrLast returns the first or last uploaded file in
// the collection based on the given collectionID and the order. // the collection based on the given collectionID and the order.
Future<EnteFile?> getCollectionFileFirstOrLast( Future<EnteFile?> getCollectionFileFirstOrLast(
@ -1643,13 +1683,14 @@ class FilesDB {
} }
Future<List<int>> getOwnedFileIDs(int ownerID) async { Future<List<int>> getOwnedFileIDs(int ownerID) async {
final db = await instance.database; final db = await instance.sqliteAsyncDB;
final results = await db.query( final results = await db.getAll(
filesTable, '''
columns: [columnUploadedFileID], SELECT DISTINCT $columnUploadedFileID FROM $filesTable
where: WHERE ($columnOwnerID = ? AND $columnUploadedFileID IS NOT NULL AND
'($columnOwnerID = $ownerID AND $columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)', $columnUploadedFileID IS NOT -1)
distinct: true, ''',
[ownerID],
); );
final ids = <int>[]; final ids = <int>[];
for (final result in results) { for (final result in results) {
@ -1659,16 +1700,17 @@ class FilesDB {
} }
Future<List<EnteFile>> getUploadedFiles(List<int> uploadedIDs) async { Future<List<EnteFile>> getUploadedFiles(List<int> uploadedIDs) async {
final db = await instance.database; final db = await instance.sqliteAsyncDB;
String inParam = ""; String inParam = "";
for (final id in uploadedIDs) { for (final id in uploadedIDs) {
inParam += "'" + id.toString() + "',"; inParam += "'" + id.toString() + "',";
} }
inParam = inParam.substring(0, inParam.length - 1); inParam = inParam.substring(0, inParam.length - 1);
final results = await db.query( final results = await db.getAll(
filesTable, '''
where: '$columnUploadedFileID IN ($inParam)', SELECT * FROM $filesTable WHERE $columnUploadedFileID IN ($inParam)
groupBy: columnUploadedFileID, GROUP BY $columnUploadedFileID
''',
); );
if (results.isEmpty) { if (results.isEmpty) {
return <EnteFile>[]; return <EnteFile>[];

View file

@ -26,4 +26,6 @@ enum EventType {
hide, hide,
unhide, unhide,
coverChanged, coverChanged,
peopleChanged,
peopleClusterChanged,
} }

View file

@ -0,0 +1,22 @@
import "package:photos/events/event.dart";
import "package:photos/models/file/file.dart";
class PeopleChangedEvent extends Event {
final List<EnteFile>? relevantFiles;
final PeopleEventType type;
final String source;
PeopleChangedEvent({
this.relevantFiles,
this.type = PeopleEventType.defaultType,
this.source = "",
});
@override
String get reason => '$runtimeType{type: ${type.name}, "via": $source}';
}
enum PeopleEventType {
defaultType,
removedFilesFromCluster,
}

View file

@ -0,0 +1,193 @@
import 'dart:math' as math show sin, cos, atan2, sqrt, pow;
import 'package:ml_linalg/linalg.dart';
extension SetVectorValues on Vector {
Vector setValues(int start, int end, Iterable<double> values) {
if (values.length > length) {
throw Exception('Values cannot be larger than vector');
} else if (end - start != values.length) {
throw Exception('Values must be same length as range');
} else if (start < 0 || end > length) {
throw Exception('Range must be within vector');
}
final tempList = toList();
tempList.replaceRange(start, end, values);
final newVector = Vector.fromList(tempList);
return newVector;
}
}
extension SetMatrixValues on Matrix {
Matrix setSubMatrix(
int startRow,
int endRow,
int startColumn,
int endColumn,
Iterable<Iterable<double>> values,
) {
if (values.length > rowCount) {
throw Exception('New values cannot have more rows than original matrix');
} else if (values.elementAt(0).length > columnCount) {
throw Exception(
'New values cannot have more columns than original matrix',
);
} else if (endRow - startRow != values.length) {
throw Exception('Values (number of rows) must be same length as range');
} else if (endColumn - startColumn != values.elementAt(0).length) {
throw Exception(
'Values (number of columns) must be same length as range',
);
} else if (startRow < 0 ||
endRow > rowCount ||
startColumn < 0 ||
endColumn > columnCount) {
throw Exception('Range must be within matrix');
}
final tempList = asFlattenedList
.toList(); // You need `.toList()` here to make sure the list is growable, otherwise `replaceRange` will throw an error
for (var i = startRow; i < endRow; i++) {
tempList.replaceRange(
i * columnCount + startColumn,
i * columnCount + endColumn,
values.elementAt(i).toList(),
);
}
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix setValues(
int startRow,
int endRow,
int startColumn,
int endColumn,
Iterable<double> values,
) {
if ((startRow - endRow) * (startColumn - endColumn) != values.length) {
throw Exception('Values must be same length as range');
} else if (startRow < 0 ||
endRow > rowCount ||
startColumn < 0 ||
endColumn > columnCount) {
throw Exception('Range must be within matrix');
}
final tempList = asFlattenedList
.toList(); // You need `.toList()` here to make sure the list is growable, otherwise `replaceRange` will throw an error
var index = 0;
for (var i = startRow; i < endRow; i++) {
for (var j = startColumn; j < endColumn; j++) {
tempList[i * columnCount + j] = values.elementAt(index);
index++;
}
}
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix setValue(int row, int column, double value) {
if (row < 0 || row > rowCount || column < 0 || column > columnCount) {
throw Exception('Index must be within range of matrix');
}
final tempList = asFlattenedList;
tempList[row * columnCount + column] = value;
final newMatrix = Matrix.fromFlattenedList(tempList, rowCount, columnCount);
return newMatrix;
}
Matrix appendRow(List<double> row) {
final oldNumberOfRows = rowCount;
final oldNumberOfColumns = columnCount;
if (row.length != oldNumberOfColumns) {
throw Exception('Row must have same number of columns as matrix');
}
final flatListMatrix = asFlattenedList;
flatListMatrix.addAll(row);
return Matrix.fromFlattenedList(
flatListMatrix,
oldNumberOfRows + 1,
oldNumberOfColumns,
);
}
}
extension MatrixCalculations on Matrix {
double determinant() {
final int length = rowCount;
if (length != columnCount) {
throw Exception('Matrix must be square');
}
if (length == 1) {
return this[0][0];
} else if (length == 2) {
return this[0][0] * this[1][1] - this[0][1] * this[1][0];
} else {
throw Exception('Determinant for Matrix larger than 2x2 not implemented');
}
}
/// Computes the singular value decomposition of a matrix, using https://lucidar.me/en/mathematics/singular-value-decomposition-of-a-2x2-matrix/ as reference, but with slightly different signs for the second columns of U and V
Map<String, dynamic> svd() {
if (rowCount != 2 || columnCount != 2) {
throw Exception('Matrix must be 2x2');
}
final a = this[0][0];
final b = this[0][1];
final c = this[1][0];
final d = this[1][1];
// Computation of U matrix
final tempCalc = a * a + b * b - c * c - d * d;
final theta = 0.5 * math.atan2(2 * a * c + 2 * b * d, tempCalc);
final U = Matrix.fromList([
[math.cos(theta), math.sin(theta)],
[math.sin(theta), -math.cos(theta)],
]);
// Computation of S matrix
// ignore: non_constant_identifier_names
final S1 = a * a + b * b + c * c + d * d;
// ignore: non_constant_identifier_names
final S2 =
math.sqrt(math.pow(tempCalc, 2) + 4 * math.pow(a * c + b * d, 2));
final sigma1 = math.sqrt((S1 + S2) / 2);
final sigma2 = math.sqrt((S1 - S2) / 2);
final S = Vector.fromList([sigma1, sigma2]);
// Computation of V matrix
final tempCalc2 = a * a - b * b + c * c - d * d;
final phi = 0.5 * math.atan2(2 * a * b + 2 * c * d, tempCalc2);
final s11 = (a * math.cos(theta) + c * math.sin(theta)) * math.cos(phi) +
(b * math.cos(theta) + d * math.sin(theta)) * math.sin(phi);
final s22 = (a * math.sin(theta) - c * math.cos(theta)) * math.sin(phi) +
(-b * math.sin(theta) + d * math.cos(theta)) * math.cos(phi);
final V = Matrix.fromList([
[s11.sign * math.cos(phi), s22.sign * math.sin(phi)],
[s11.sign * math.sin(phi), -s22.sign * math.cos(phi)],
]);
return {
'U': U,
'S': S,
'V': V,
};
}
int matrixRank() {
final svdResult = svd();
final Vector S = svdResult['S']!;
final rank = S.toList().where((element) => element > 1e-10).length;
return rank;
}
}
extension TransformMatrix on Matrix {
List<List<double>> to2DList() {
final List<List<double>> outerList = [];
for (var i = 0; i < rowCount; i++) {
final innerList = this[i].toList();
outerList.add(innerList);
}
return outerList;
}
}

View file

@ -23,4 +23,9 @@ class EnteWatch extends Stopwatch {
reset(); reset();
previousElapsed = 0; previousElapsed = 0;
} }
void stopWithLog(String msg) {
log(msg);
stop();
}
} }

1051
mobile/lib/face/db.dart Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,103 @@
// Faces Table Fields & Schema Queries
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
const facesTable = 'faces';
const fileIDColumn = 'file_id';
const faceIDColumn = 'face_id';
const faceDetectionColumn = 'detection';
const faceEmbeddingBlob = 'eBlob';
const faceScore = 'score';
const faceBlur = 'blur';
const isSideways = 'is_sideways';
const imageWidth = 'width';
const imageHeight = 'height';
const faceClusterId = 'cluster_id';
const mlVersionColumn = 'ml_version';
const createFacesTable = '''CREATE TABLE IF NOT EXISTS $facesTable (
$fileIDColumn INTEGER NOT NULL,
$faceIDColumn TEXT NOT NULL UNIQUE,
$faceDetectionColumn TEXT NOT NULL,
$faceEmbeddingBlob BLOB NOT NULL,
$faceScore REAL NOT NULL,
$faceBlur REAL NOT NULL DEFAULT $kLapacianDefault,
$isSideways INTEGER NOT NULL DEFAULT 0,
$imageHeight INTEGER NOT NULL DEFAULT 0,
$imageWidth INTEGER NOT NULL DEFAULT 0,
$mlVersionColumn INTEGER NOT NULL DEFAULT -1,
PRIMARY KEY($fileIDColumn, $faceIDColumn)
);
''';
const deleteFacesTable = 'DROP TABLE IF EXISTS $facesTable';
// End of Faces Table Fields & Schema Queries
//##region Face Clusters Table Fields & Schema Queries
const faceClustersTable = 'face_clusters';
const fcClusterID = 'cluster_id';
const fcFaceId = 'face_id';
// fcClusterId & fcFaceId are the primary keys and fcClusterId is a foreign key to faces table
const createFaceClustersTable = '''
CREATE TABLE IF NOT EXISTS $faceClustersTable (
$fcFaceId TEXT NOT NULL,
$fcClusterID INTEGER NOT NULL,
PRIMARY KEY($fcFaceId)
);
''';
// -- Creating a non-unique index on clusterID for query optimization
const fcClusterIDIndex =
'''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);''';
const dropFaceClustersTable = 'DROP TABLE IF EXISTS $faceClustersTable';
//##endregion
// People Table Fields & Schema Queries
const personTable = 'person';
const deletePersonTable = 'DROP TABLE IF EXISTS $personTable';
//End People Table Fields & Schema Queries
// Clusters Table Fields & Schema Queries
const clusterPersonTable = 'cluster_person';
const personIdColumn = 'person_id';
const clusterIDColumn = 'cluster_id';
const createClusterPersonTable = '''
CREATE TABLE IF NOT EXISTS $clusterPersonTable (
$personIdColumn TEXT NOT NULL,
$clusterIDColumn INTEGER NOT NULL,
PRIMARY KEY($personIdColumn, $clusterIDColumn)
);
''';
const dropClusterPersonTable = 'DROP TABLE IF EXISTS $clusterPersonTable';
// End Clusters Table Fields & Schema Queries
/// Cluster Summary Table Fields & Schema Queries
const clusterSummaryTable = 'cluster_summary';
const avgColumn = 'avg';
const countColumn = 'count';
const createClusterSummaryTable = '''
CREATE TABLE IF NOT EXISTS $clusterSummaryTable (
$clusterIDColumn INTEGER NOT NULL,
$avgColumn BLOB NOT NULL,
$countColumn INTEGER NOT NULL,
PRIMARY KEY($clusterIDColumn)
);
''';
const dropClusterSummaryTable = 'DROP TABLE IF EXISTS $clusterSummaryTable';
/// End Cluster Summary Table Fields & Schema Queries
/// notPersonFeedback Table Fields & Schema Queries
const notPersonFeedback = 'not_person_feedback';
const createNotPersonFeedbackTable = '''
CREATE TABLE IF NOT EXISTS $notPersonFeedback (
$personIdColumn TEXT NOT NULL,
$clusterIDColumn INTEGER NOT NULL,
PRIMARY KEY($personIdColumn, $clusterIDColumn)
);
''';
const dropNotPersonFeedbackTable = 'DROP TABLE IF EXISTS $notPersonFeedback';
// End Clusters Table Fields & Schema Queries

View file

@ -0,0 +1,57 @@
import "dart:convert";
import 'package:photos/face/db_fields.dart';
import "package:photos/face/model/detection.dart";
import "package:photos/face/model/face.dart";
import "package:photos/generated/protos/ente/common/vector.pb.dart";
import "package:photos/models/ml/ml_versions.dart";
int boolToSQLInt(bool? value, {bool defaultValue = false}) {
final bool v = value ?? defaultValue;
if (v == false) {
return 0;
} else {
return 1;
}
}
bool sqlIntToBool(int? value, {bool defaultValue = false}) {
final int v = value ?? (defaultValue ? 1 : 0);
if (v == 0) {
return false;
} else {
return true;
}
}
Map<String, dynamic> mapRemoteToFaceDB(Face face) {
return {
faceIDColumn: face.faceID,
fileIDColumn: face.fileID,
faceDetectionColumn: json.encode(face.detection.toJson()),
faceEmbeddingBlob: EVector(
values: face.embedding,
).writeToBuffer(),
faceScore: face.score,
faceBlur: face.blur,
isSideways: face.detection.faceIsSideways() ? 1 : 0,
mlVersionColumn: faceMlVersion,
imageWidth: face.fileInfo?.imageWidth ?? 0,
imageHeight: face.fileInfo?.imageHeight ?? 0,
};
}
Face mapRowToFace(Map<String, dynamic> row) {
return Face(
row[faceIDColumn] as String,
row[fileIDColumn] as int,
EVector.fromBuffer(row[faceEmbeddingBlob] as List<int>).values,
row[faceScore] as double,
Detection.fromJson(json.decode(row[faceDetectionColumn] as String)),
row[faceBlur] as double,
fileInfo: FileInfo(
imageWidth: row[imageWidth] as int,
imageHeight: row[imageHeight] as int,
),
);
}

View file

@ -0,0 +1,35 @@
/// Bounding box of a face.
///
/// [ x] and [y] are the minimum coordinates, so the top left corner of the box.
/// [width] and [height] are the width and height of the box.
///
/// WARNING: All values are relative to the original image size, so in the range [0, 1].
class FaceBox {
final double x;
final double y;
final double width;
final double height;
FaceBox({
required this.x,
required this.y,
required this.width,
required this.height,
});
factory FaceBox.fromJson(Map<String, dynamic> json) {
return FaceBox(
x: (json['x'] as double?) ?? (json['xMin'] as double),
y: (json['y'] as double?) ?? (json['yMin'] as double),
width: json['width'] as double,
height: json['height'] as double,
);
}
Map<String, dynamic> toJson() => {
'x': x,
'y': y,
'width': width,
'height': height,
};
}

View file

@ -0,0 +1,120 @@
import "dart:math" show min, max;
import "package:photos/face/model/box.dart";
import "package:photos/face/model/landmark.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
/// Stores the face detection data, notably the bounding box and landmarks.
///
/// - Bounding box: [FaceBox] with x, y (minimum, so top left corner), width, height
/// - Landmarks: list of [Landmark]s, namely leftEye, rightEye, nose, leftMouth, rightMouth
///
/// WARNING: All coordinates are relative to the image size, so in the range [0, 1]!
class Detection {
FaceBox box;
List<Landmark> landmarks;
Detection({
required this.box,
required this.landmarks,
});
bool get isEmpty => box.width == 0 && box.height == 0 && landmarks.isEmpty;
// empty box
Detection.empty()
: box = FaceBox(
x: 0,
y: 0,
width: 0,
height: 0,
),
landmarks = [];
Map<String, dynamic> toJson() => {
'box': box.toJson(),
'landmarks': landmarks.map((x) => x.toJson()).toList(),
};
factory Detection.fromJson(Map<String, dynamic> json) {
return Detection(
box: FaceBox.fromJson(json['box'] as Map<String, dynamic>),
landmarks: List<Landmark>.from(
json['landmarks']
.map((x) => Landmark.fromJson(x as Map<String, dynamic>)),
),
);
}
int getFaceArea(int imageWidth, int imageHeight) {
return (box.width * imageWidth * box.height * imageHeight).toInt();
}
FaceDirection getFaceDirection() {
if (isEmpty) {
return FaceDirection.straight;
}
final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y];
final leftMouth = [landmarks[3].x, landmarks[3].y];
final rightMouth = [landmarks[4].x, landmarks[4].y];
final double eyeDistanceX = (rightEye[0] - leftEye[0]).abs();
final double eyeDistanceY = (rightEye[1] - leftEye[1]).abs();
final double mouthDistanceY = (rightMouth[1] - leftMouth[1]).abs();
final bool faceIsUpright =
(max(leftEye[1], rightEye[1]) + 0.5 * eyeDistanceY < nose[1]) &&
(nose[1] + 0.5 * mouthDistanceY < min(leftMouth[1], rightMouth[1]));
final bool noseStickingOutLeft = (nose[0] < min(leftEye[0], rightEye[0])) &&
(nose[0] < min(leftMouth[0], rightMouth[0]));
final bool noseStickingOutRight =
(nose[0] > max(leftEye[0], rightEye[0])) &&
(nose[0] > max(leftMouth[0], rightMouth[0]));
final bool noseCloseToLeftEye =
(nose[0] - leftEye[0]).abs() < 0.2 * eyeDistanceX;
final bool noseCloseToRightEye =
(nose[0] - rightEye[0]).abs() < 0.2 * eyeDistanceX;
// if (faceIsUpright && (noseStickingOutLeft || noseCloseToLeftEye)) {
if (noseStickingOutLeft || (faceIsUpright && noseCloseToLeftEye)) {
return FaceDirection.left;
// } else if (faceIsUpright && (noseStickingOutRight || noseCloseToRightEye)) {
} else if (noseStickingOutRight || (faceIsUpright && noseCloseToRightEye)) {
return FaceDirection.right;
}
return FaceDirection.straight;
}
bool faceIsSideways() {
if (isEmpty) {
return false;
}
final leftEye = [landmarks[0].x, landmarks[0].y];
final rightEye = [landmarks[1].x, landmarks[1].y];
final nose = [landmarks[2].x, landmarks[2].y];
final leftMouth = [landmarks[3].x, landmarks[3].y];
final rightMouth = [landmarks[4].x, landmarks[4].y];
final double eyeDistanceX = (rightEye[0] - leftEye[0]).abs();
final double eyeDistanceY = (rightEye[1] - leftEye[1]).abs();
final double mouthDistanceY = (rightMouth[1] - leftMouth[1]).abs();
final bool faceIsUpright =
(max(leftEye[1], rightEye[1]) + 0.5 * eyeDistanceY < nose[1]) &&
(nose[1] + 0.5 * mouthDistanceY < min(leftMouth[1], rightMouth[1]));
final bool noseStickingOutLeft =
(nose[0] < min(leftEye[0], rightEye[0]) - 0.5 * eyeDistanceX) &&
(nose[0] < min(leftMouth[0], rightMouth[0]));
final bool noseStickingOutRight =
(nose[0] > max(leftEye[0], rightEye[0]) + 0.5 * eyeDistanceX) &&
(nose[0] > max(leftMouth[0], rightMouth[0]));
return faceIsUpright && (noseStickingOutLeft || noseStickingOutRight);
}
}

View file

@ -0,0 +1,25 @@
class Dimensions {
final int width;
final int height;
const Dimensions({required this.width, required this.height});
@override
String toString() {
return 'Dimensions(width: $width, height: $height})';
}
Map<String, int> toJson() {
return {
'width': width,
'height': height,
};
}
factory Dimensions.fromJson(Map<String, dynamic> json) {
return Dimensions(
width: json['width'] as int,
height: json['height'] as int,
);
}
}

View file

@ -0,0 +1,85 @@
import "package:photos/face/model/detection.dart";
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
import "package:photos/services/machine_learning/face_ml/face_ml_result.dart";
// FileInfo contains the image width and height of the image the face was detected in.
class FileInfo {
int? imageWidth;
int? imageHeight;
FileInfo({
this.imageWidth,
this.imageHeight,
});
}
class Face {
final String faceID;
final List<double> embedding;
Detection detection;
final double score;
final double blur;
///#region Local DB fields
// This is not stored on the server, using it for local DB row
FileInfo? fileInfo;
final int fileID;
///#endregion
bool get isBlurry => blur < kLaplacianHardThreshold;
bool get hasHighScore => score > kMinimumQualityFaceScore;
bool get isHighQuality => (!isBlurry) && hasHighScore;
int area({int? w, int? h}) {
return detection.getFaceArea(
fileInfo?.imageWidth ?? w ?? 0,
fileInfo?.imageHeight ?? h ?? 0,
);
}
Face(
this.faceID,
this.fileID,
this.embedding,
this.score,
this.detection,
this.blur, {
this.fileInfo,
});
factory Face.empty(int fileID, {bool error = false}) {
return Face(
"$fileID-0",
fileID,
<double>[],
error ? -1.0 : 0.0,
Detection.empty(),
0.0,
);
}
factory Face.fromJson(Map<String, dynamic> json) {
final String faceID = json['faceID'] as String;
final int fileID = getFileIdFromFaceId(faceID);
return Face(
faceID,
fileID,
List<double>.from((json['embedding'] ?? json['embeddings']) as List),
json['score'] as double,
Detection.fromJson(json['detection'] as Map<String, dynamic>),
// high value means t
(json['blur'] ?? kLapacianDefault) as double,
);
}
// Note: Keep the information in toJson minimum. Keep in sync with desktop.
// Derive fields like fileID from other values whenever possible
Map<String, dynamic> toJson() => {
'faceID': faceID,
'embedding': embedding,
'detection': detection.toJson(),
'score': score,
'blur': blur,
};
}

View file

@ -0,0 +1,33 @@
/// Landmark coordinate data.
///
/// WARNING: All coordinates are relative to the image size, so in the range [0, 1]!
class Landmark {
double x;
double y;
Landmark({
required this.x,
required this.y,
});
Map<String, dynamic> toJson() => {
'x': x,
'y': y,
};
factory Landmark.fromJson(Map<String, dynamic> json) {
return Landmark(
x: (json['x'] is int
? (json['x'] as int).toDouble()
: json['x'] as double),
y: (json['y'] is int
? (json['y'] as int).toDouble()
: json['y'] as double),
);
}
@override
toString() {
return '(x: ${x.toStringAsFixed(4)}, y: ${y.toStringAsFixed(4)})';
}
}

View file

@ -0,0 +1,139 @@
// PersonEntity represents information about a Person in the context of FaceClustering that is stored.
// On the remote server, the PersonEntity is stored as {Entity} with type person.
// On the device, this information is stored as [LocalEntityData] with type person.
import "package:flutter/foundation.dart";
class PersonEntity {
final String remoteID;
final PersonData data;
PersonEntity(
this.remoteID,
this.data,
);
// copyWith
PersonEntity copyWith({
String? remoteID,
PersonData? data,
}) {
return PersonEntity(
remoteID ?? this.remoteID,
data ?? this.data,
);
}
}
class ClusterInfo {
final int id;
final Set<String> faces;
ClusterInfo({
required this.id,
required this.faces,
});
// toJson
Map<String, dynamic> toJson() => {
'id': id,
'faces': faces.toList(),
};
// from Json
factory ClusterInfo.fromJson(Map<String, dynamic> json) {
return ClusterInfo(
id: json['id'] as int,
faces: (json['faces'] as List<dynamic>).map((e) => e as String).toSet(),
);
}
}
class PersonData {
final String name;
final bool isHidden;
String? avatarFaceId;
List<ClusterInfo>? assigned = List<ClusterInfo>.empty();
List<ClusterInfo>? rejected = List<ClusterInfo>.empty();
final String? birthDate;
bool hasAvatar() => avatarFaceId != null;
bool get isIgnored =>
(name.isEmpty || name == '(hidden)' || name == '(ignored)');
PersonData({
required this.name,
this.assigned,
this.rejected,
this.avatarFaceId,
this.isHidden = false,
this.birthDate,
});
// copyWith
PersonData copyWith({
String? name,
List<ClusterInfo>? assigned,
String? avatarFaceId,
bool? isHidden,
int? version,
String? birthDate,
}) {
return PersonData(
name: name ?? this.name,
assigned: assigned ?? this.assigned,
avatarFaceId: avatarFaceId ?? this.avatarFaceId,
isHidden: isHidden ?? this.isHidden,
birthDate: birthDate ?? this.birthDate,
);
}
void logStats() {
if (kDebugMode == false) return;
// log number of assigned and rejected clusters and total number of faces in each cluster
final StringBuffer sb = StringBuffer();
sb.writeln('Person: $name');
int assignedCount = 0;
for (final a in (assigned ?? <ClusterInfo>[])) {
assignedCount += a.faces.length;
}
sb.writeln('Assigned: ${assigned?.length} withFaces $assignedCount');
sb.writeln('Rejected: ${rejected?.length}');
if (assigned != null) {
for (var cluster in assigned!) {
sb.writeln('Cluster: ${cluster.id} - ${cluster.faces.length}');
}
}
debugPrint(sb.toString());
}
// toJson
Map<String, dynamic> toJson() => {
'name': name,
'assigned': assigned?.map((e) => e.toJson()).toList(),
'rejected': rejected?.map((e) => e.toJson()).toList(),
'avatarFaceId': avatarFaceId,
'isHidden': isHidden,
'birthDate': birthDate,
};
// fromJson
factory PersonData.fromJson(Map<String, dynamic> json) {
final assigned = (json['assigned'] == null || json['assigned'].length == 0)
? <ClusterInfo>[]
: List<ClusterInfo>.from(
json['assigned'].map((x) => ClusterInfo.fromJson(x)),
);
final rejected = (json['rejected'] == null || json['rejected'].length == 0)
? <ClusterInfo>[]
: List<ClusterInfo>.from(
json['rejected'].map((x) => ClusterInfo.fromJson(x)),
);
return PersonData(
name: json['name'] as String,
assigned: assigned,
rejected: rejected,
avatarFaceId: json['avatarFaceId'] as String?,
isHidden: json['isHidden'] as bool? ?? false,
birthDate: json['birthDate'] as String?,
);
}
}

View file

@ -34,6 +34,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addViewers": m1, "addViewers": m1,
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"), "Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"createCollaborativeLink": "createCollaborativeLink":
MessageLookupByLibrary.simpleMessage("Create collaborative link"), MessageLookupByLibrary.simpleMessage("Create collaborative link"),
@ -44,7 +46,16 @@ class MessageLookup extends MessageLookupByLibrary {
"editsToLocationWillOnlyBeSeenWithinEnte": "editsToLocationWillOnlyBeSeenWithinEnte":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"), "Edits to location will only be seen within Ente"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
"locations": MessageLookupByLibrary.simpleMessage("Locations"), "locations": MessageLookupByLibrary.simpleMessage("Locations"),
"longPressAnEmailToVerifyEndToEndEncryption": "longPressAnEmailToVerifyEndToEndEncryption":
@ -55,6 +66,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"), "Modify your query, or try searching for"),
"moveToHiddenAlbum": "moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"), MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"), "search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation": "selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"), MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -227,6 +227,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Ich verstehe, dass ich meine Daten verlieren kann, wenn ich mein Passwort vergesse, da meine Daten <underline>Ende-zu-Ende-verschlüsselt</underline> sind."), "Ich verstehe, dass ich meine Daten verlieren kann, wenn ich mein Passwort vergesse, da meine Daten <underline>Ende-zu-Ende-verschlüsselt</underline> sind."),
"activeSessions": "activeSessions":
MessageLookupByLibrary.simpleMessage("Aktive Sitzungen"), MessageLookupByLibrary.simpleMessage("Aktive Sitzungen"),
"addAName": MessageLookupByLibrary.simpleMessage("Add a name"),
"addANewEmail": MessageLookupByLibrary.simpleMessage( "addANewEmail": MessageLookupByLibrary.simpleMessage(
"Neue E-Mail-Adresse hinzufügen"), "Neue E-Mail-Adresse hinzufügen"),
"addCollaborator": "addCollaborator":
@ -435,6 +436,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Nach Aufnahmezeit gruppieren"), "Nach Aufnahmezeit gruppieren"),
"clubByFileName": "clubByFileName":
MessageLookupByLibrary.simpleMessage("Nach Dateiname gruppieren"), MessageLookupByLibrary.simpleMessage("Nach Dateiname gruppieren"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code eingelöst"), MessageLookupByLibrary.simpleMessage("Code eingelöst"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -675,6 +678,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Passwort eingeben"), MessageLookupByLibrary.simpleMessage("Passwort eingeben"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Gib ein Passwort ein, mit dem wir deine Daten verschlüsseln können"), "Gib ein Passwort ein, mit dem wir deine Daten verschlüsseln können"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage( "enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Gib den Weiterempfehlungs-Code ein"), "Gib den Weiterempfehlungs-Code ein"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -699,6 +704,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Protokolle exportieren"), MessageLookupByLibrary.simpleMessage("Protokolle exportieren"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Daten exportieren"), MessageLookupByLibrary.simpleMessage("Daten exportieren"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gesichter"), "faces": MessageLookupByLibrary.simpleMessage("Gesichter"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage( "failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Der Code konnte nicht aktiviert werden"), "Der Code konnte nicht aktiviert werden"),
@ -738,11 +747,14 @@ class MessageLookup extends MessageLookupByLibrary {
"filesBackedUpInAlbum": m23, "filesBackedUpInAlbum": m23,
"filesDeleted": "filesDeleted":
MessageLookupByLibrary.simpleMessage("Dateien gelöscht"), MessageLookupByLibrary.simpleMessage("Dateien gelöscht"),
"findPeopleByName": MessageLookupByLibrary.simpleMessage(
"Find people quickly by searching by name"),
"flip": MessageLookupByLibrary.simpleMessage("Spiegeln"), "flip": MessageLookupByLibrary.simpleMessage("Spiegeln"),
"forYourMemories": "forYourMemories":
MessageLookupByLibrary.simpleMessage("Als Erinnerung"), MessageLookupByLibrary.simpleMessage("Als Erinnerung"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Passwort vergessen"), MessageLookupByLibrary.simpleMessage("Passwort vergessen"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage( "freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Kostenlos hinzugefügter Speicherplatz"), "Kostenlos hinzugefügter Speicherplatz"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -807,6 +819,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Falscher Wiederherstellungs-Schlüssel"), "Falscher Wiederherstellungs-Schlüssel"),
"indexedItems": "indexedItems":
MessageLookupByLibrary.simpleMessage("Indizierte Elemente"), MessageLookupByLibrary.simpleMessage("Indizierte Elemente"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"), MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"),
"installManually": "installManually":
@ -1164,6 +1178,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Teilnehmer entfernen"), MessageLookupByLibrary.simpleMessage("Teilnehmer entfernen"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Öffentlichen Link entfernen"), MessageLookupByLibrary.simpleMessage("Öffentlichen Link entfernen"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -132,7 +132,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Please talk to ${providerName} support if you were charged"; "Please talk to ${providerName} support if you were charged";
static String m38(endDate) => static String m38(endDate) =>
"Free trial valid till ${endDate}.\nYou can purchase a paid plan afterwards."; "Free trial valid till ${endDate}.\nYou can choose a paid plan afterwards.";
static String m39(toEmail) => "Please email us at ${toEmail}"; static String m39(toEmail) => "Please email us at ${toEmail}";
@ -225,6 +225,7 @@ class MessageLookup extends MessageLookupByLibrary {
"I understand that if I lose my password, I may lose my data since my data is <underline>end-to-end encrypted</underline>."), "I understand that if I lose my password, I may lose my data since my data is <underline>end-to-end encrypted</underline>."),
"activeSessions": "activeSessions":
MessageLookupByLibrary.simpleMessage("Active sessions"), MessageLookupByLibrary.simpleMessage("Active sessions"),
"addAName": MessageLookupByLibrary.simpleMessage("Add a name"),
"addANewEmail": MessageLookupByLibrary.simpleMessage("Add a new email"), "addANewEmail": MessageLookupByLibrary.simpleMessage("Add a new email"),
"addCollaborator": "addCollaborator":
MessageLookupByLibrary.simpleMessage("Add collaborator"), MessageLookupByLibrary.simpleMessage("Add collaborator"),
@ -434,6 +435,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Club by capture time"), MessageLookupByLibrary.simpleMessage("Club by capture time"),
"clubByFileName": "clubByFileName":
MessageLookupByLibrary.simpleMessage("Club by file name"), MessageLookupByLibrary.simpleMessage("Club by file name"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code applied"), MessageLookupByLibrary.simpleMessage("Code applied"),
"codeCopiedToClipboard": "codeCopiedToClipboard":
@ -675,6 +678,8 @@ class MessageLookup extends MessageLookupByLibrary {
"enterPassword": MessageLookupByLibrary.simpleMessage("Enter password"), "enterPassword": MessageLookupByLibrary.simpleMessage("Enter password"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Enter a password we can use to encrypt your data"), "Enter a password we can use to encrypt your data"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": "enterReferralCode":
MessageLookupByLibrary.simpleMessage("Enter referral code"), MessageLookupByLibrary.simpleMessage("Enter referral code"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -697,6 +702,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"), "exportLogs": MessageLookupByLibrary.simpleMessage("Export logs"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Export your data"), MessageLookupByLibrary.simpleMessage("Export your data"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Faces"), "faces": MessageLookupByLibrary.simpleMessage("Faces"),
"failedToApplyCode": "failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Failed to apply code"), MessageLookupByLibrary.simpleMessage("Failed to apply code"),
@ -736,11 +745,14 @@ class MessageLookup extends MessageLookupByLibrary {
"filesDeleted": MessageLookupByLibrary.simpleMessage("Files deleted"), "filesDeleted": MessageLookupByLibrary.simpleMessage("Files deleted"),
"filesSavedToGallery": "filesSavedToGallery":
MessageLookupByLibrary.simpleMessage("Files saved to gallery"), MessageLookupByLibrary.simpleMessage("Files saved to gallery"),
"findPeopleByName":
MessageLookupByLibrary.simpleMessage("Find people quickly by name"),
"flip": MessageLookupByLibrary.simpleMessage("Flip"), "flip": MessageLookupByLibrary.simpleMessage("Flip"),
"forYourMemories": "forYourMemories":
MessageLookupByLibrary.simpleMessage("for your memories"), MessageLookupByLibrary.simpleMessage("for your memories"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Forgot password"), MessageLookupByLibrary.simpleMessage("Forgot password"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": "freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Free storage claimed"), MessageLookupByLibrary.simpleMessage("Free storage claimed"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -801,6 +813,8 @@ class MessageLookup extends MessageLookupByLibrary {
"incorrectRecoveryKeyTitle": "incorrectRecoveryKeyTitle":
MessageLookupByLibrary.simpleMessage("Incorrect recovery key"), MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
"indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Insecure device"), MessageLookupByLibrary.simpleMessage("Insecure device"),
"installManually": "installManually":
@ -1022,6 +1036,7 @@ class MessageLookup extends MessageLookupByLibrary {
"paymentFailedTalkToProvider": m37, "paymentFailedTalkToProvider": m37,
"pendingItems": MessageLookupByLibrary.simpleMessage("Pending items"), "pendingItems": MessageLookupByLibrary.simpleMessage("Pending items"),
"pendingSync": MessageLookupByLibrary.simpleMessage("Pending sync"), "pendingSync": MessageLookupByLibrary.simpleMessage("Pending sync"),
"people": MessageLookupByLibrary.simpleMessage("People"),
"peopleUsingYourCode": "peopleUsingYourCode":
MessageLookupByLibrary.simpleMessage("People using your code"), MessageLookupByLibrary.simpleMessage("People using your code"),
"permDeleteWarning": MessageLookupByLibrary.simpleMessage( "permDeleteWarning": MessageLookupByLibrary.simpleMessage(
@ -1151,6 +1166,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Remove participant"), MessageLookupByLibrary.simpleMessage("Remove participant"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Remove public link"), MessageLookupByLibrary.simpleMessage("Remove public link"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(
@ -1208,8 +1225,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Add descriptions like \"#trip\" in photo info to quickly find them here"), "Add descriptions like \"#trip\" in photo info to quickly find them here"),
"searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
"Search by a date, month or year"), "Search by a date, month or year"),
"searchFaceEmptySection": "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
MessageLookupByLibrary.simpleMessage("Find all photos of a person"), "Persons will be shown here once indexing is done"),
"searchFileTypesAndNamesEmptySection": "searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("File types and names"), MessageLookupByLibrary.simpleMessage("File types and names"),
"searchHint1": "searchHint1":

View file

@ -367,6 +367,8 @@ class MessageLookup extends MessageLookupByLibrary {
"close": MessageLookupByLibrary.simpleMessage("Cerrar"), "close": MessageLookupByLibrary.simpleMessage("Cerrar"),
"clubByCaptureTime": MessageLookupByLibrary.simpleMessage( "clubByCaptureTime": MessageLookupByLibrary.simpleMessage(
"Agrupar por tiempo de captura"), "Agrupar por tiempo de captura"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Código aplicado"), MessageLookupByLibrary.simpleMessage("Código aplicado"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -585,6 +587,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Introduzca contraseña"), MessageLookupByLibrary.simpleMessage("Introduzca contraseña"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Introduzca una contraseña que podamos usar para cifrar sus datos"), "Introduzca una contraseña que podamos usar para cifrar sus datos"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage( "enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Ingresar código de referencia"), "Ingresar código de referencia"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -609,6 +613,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Exportar registros"), MessageLookupByLibrary.simpleMessage("Exportar registros"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Exportar tus datos"), MessageLookupByLibrary.simpleMessage("Exportar tus datos"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode": "failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Error al aplicar el código"), MessageLookupByLibrary.simpleMessage("Error al aplicar el código"),
"failedToCancel": "failedToCancel":
@ -647,6 +655,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("para tus recuerdos"), MessageLookupByLibrary.simpleMessage("para tus recuerdos"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Olvidé mi contraseña"), MessageLookupByLibrary.simpleMessage("Olvidé mi contraseña"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage( "freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Almacenamiento gratuito reclamado"), "Almacenamiento gratuito reclamado"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -690,6 +699,8 @@ class MessageLookup extends MessageLookupByLibrary {
"La clave de recuperación introducida es incorrecta"), "La clave de recuperación introducida es incorrecta"),
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
"Clave de recuperación incorrecta"), "Clave de recuperación incorrecta"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"), MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"),
"installManually": "installManually":
@ -997,6 +1008,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Quitar participante"), MessageLookupByLibrary.simpleMessage("Quitar participante"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Quitar enlace público"), MessageLookupByLibrary.simpleMessage("Quitar enlace público"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -425,6 +425,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Grouper par durée"), MessageLookupByLibrary.simpleMessage("Grouper par durée"),
"clubByFileName": "clubByFileName":
MessageLookupByLibrary.simpleMessage("Grouper par nom de fichier"), MessageLookupByLibrary.simpleMessage("Grouper par nom de fichier"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code appliqué"), MessageLookupByLibrary.simpleMessage("Code appliqué"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -665,6 +667,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Saisissez le mot de passe"), MessageLookupByLibrary.simpleMessage("Saisissez le mot de passe"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Entrez un mot de passe que nous pouvons utiliser pour chiffrer vos données"), "Entrez un mot de passe que nous pouvons utiliser pour chiffrer vos données"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage( "enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Entrez le code de parrainage"), "Entrez le code de parrainage"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -688,6 +692,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"), "exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Exportez vos données"), MessageLookupByLibrary.simpleMessage("Exportez vos données"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Visages"), "faces": MessageLookupByLibrary.simpleMessage("Visages"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage( "failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossible d\'appliquer le code"), "Impossible d\'appliquer le code"),
@ -732,6 +740,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("pour vos souvenirs"), MessageLookupByLibrary.simpleMessage("pour vos souvenirs"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Mot de passe oublié"), MessageLookupByLibrary.simpleMessage("Mot de passe oublié"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": "freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Stockage gratuit réclamé"), MessageLookupByLibrary.simpleMessage("Stockage gratuit réclamé"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -795,6 +804,8 @@ class MessageLookup extends MessageLookupByLibrary {
"La clé de secours que vous avez entrée est incorrecte"), "La clé de secours que vous avez entrée est incorrecte"),
"incorrectRecoveryKeyTitle": "incorrectRecoveryKeyTitle":
MessageLookupByLibrary.simpleMessage("Clé de secours non valide"), MessageLookupByLibrary.simpleMessage("Clé de secours non valide"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Appareil non sécurisé"), MessageLookupByLibrary.simpleMessage("Appareil non sécurisé"),
"installManually": "installManually":
@ -1129,6 +1140,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Supprimer le participant"), MessageLookupByLibrary.simpleMessage("Supprimer le participant"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Supprimer le lien public"), MessageLookupByLibrary.simpleMessage("Supprimer le lien public"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -411,6 +411,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Club per tempo di cattura"), MessageLookupByLibrary.simpleMessage("Club per tempo di cattura"),
"clubByFileName": "clubByFileName":
MessageLookupByLibrary.simpleMessage("Unisci per nome file"), MessageLookupByLibrary.simpleMessage("Unisci per nome file"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Codice applicato"), MessageLookupByLibrary.simpleMessage("Codice applicato"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -644,6 +646,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Inserisci password"), MessageLookupByLibrary.simpleMessage("Inserisci password"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Inserisci una password per criptare i tuoi dati"), "Inserisci una password per criptare i tuoi dati"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage( "enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Inserisci il codice di invito"), "Inserisci il codice di invito"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -665,6 +669,10 @@ class MessageLookup extends MessageLookupByLibrary {
"Questo link è scaduto. Si prega di selezionare un nuovo orario di scadenza o disabilitare la scadenza del link."), "Questo link è scaduto. Si prega di selezionare un nuovo orario di scadenza o disabilitare la scadenza del link."),
"exportLogs": MessageLookupByLibrary.simpleMessage("Esporta log"), "exportLogs": MessageLookupByLibrary.simpleMessage("Esporta log"),
"exportYourData": MessageLookupByLibrary.simpleMessage("Esporta dati"), "exportYourData": MessageLookupByLibrary.simpleMessage("Esporta dati"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage( "failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossibile applicare il codice"), "Impossibile applicare il codice"),
"failedToCancel": "failedToCancel":
@ -704,6 +712,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("per i tuoi ricordi"), MessageLookupByLibrary.simpleMessage("per i tuoi ricordi"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Password dimenticata"), MessageLookupByLibrary.simpleMessage("Password dimenticata"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": "freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Spazio gratuito richiesto"), MessageLookupByLibrary.simpleMessage("Spazio gratuito richiesto"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -764,6 +773,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Il codice che hai inserito non è corretto"), "Il codice che hai inserito non è corretto"),
"incorrectRecoveryKeyTitle": "incorrectRecoveryKeyTitle":
MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"), MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Dispositivo non sicuro"), MessageLookupByLibrary.simpleMessage("Dispositivo non sicuro"),
"installManually": "installManually":
@ -1090,6 +1101,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Rimuovi partecipante"), MessageLookupByLibrary.simpleMessage("Rimuovi partecipante"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Rimuovi link pubblico"), MessageLookupByLibrary.simpleMessage("Rimuovi link pubblico"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(

View file

@ -34,6 +34,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addViewers": m1, "addViewers": m1,
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"), "Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"), "contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"createCollaborativeLink": "createCollaborativeLink":
MessageLookupByLibrary.simpleMessage("Create collaborative link"), MessageLookupByLibrary.simpleMessage("Create collaborative link"),
@ -44,7 +46,16 @@ class MessageLookup extends MessageLookupByLibrary {
"editsToLocationWillOnlyBeSeenWithinEnte": "editsToLocationWillOnlyBeSeenWithinEnte":
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"), "Edits to location will only be seen within Ente"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
"locations": MessageLookupByLibrary.simpleMessage("Locations"), "locations": MessageLookupByLibrary.simpleMessage("Locations"),
"longPressAnEmailToVerifyEndToEndEncryption": "longPressAnEmailToVerifyEndToEndEncryption":
@ -55,6 +66,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"), "Modify your query, or try searching for"),
"moveToHiddenAlbum": "moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"), MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"), "search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation": "selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"), MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -447,6 +447,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Samenvoegen op tijd"), MessageLookupByLibrary.simpleMessage("Samenvoegen op tijd"),
"clubByFileName": "clubByFileName":
MessageLookupByLibrary.simpleMessage("Samenvoegen op bestandsnaam"), MessageLookupByLibrary.simpleMessage("Samenvoegen op bestandsnaam"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Code toegepast"), MessageLookupByLibrary.simpleMessage("Code toegepast"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -723,6 +725,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Logboek exporteren"), MessageLookupByLibrary.simpleMessage("Logboek exporteren"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"), MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("Gezichten"), "faces": MessageLookupByLibrary.simpleMessage("Gezichten"),
"failedToApplyCode": "failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Code toepassen mislukt"), MessageLookupByLibrary.simpleMessage("Code toepassen mislukt"),
@ -771,6 +777,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("voor uw herinneringen"), MessageLookupByLibrary.simpleMessage("voor uw herinneringen"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Wachtwoord vergeten"), MessageLookupByLibrary.simpleMessage("Wachtwoord vergeten"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": "freeStorageClaimed":
MessageLookupByLibrary.simpleMessage("Gratis opslag geclaimd"), MessageLookupByLibrary.simpleMessage("Gratis opslag geclaimd"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -833,6 +840,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"), MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"),
"indexedItems": "indexedItems":
MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"), MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Onveilig apparaat"), MessageLookupByLibrary.simpleMessage("Onveilig apparaat"),
"installManually": "installManually":

View file

@ -39,6 +39,8 @@ class MessageLookup extends MessageLookupByLibrary {
"cancel": MessageLookupByLibrary.simpleMessage("Avbryt"), "cancel": MessageLookupByLibrary.simpleMessage("Avbryt"),
"changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage(
"Change location of selected items?"), "Change location of selected items?"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"confirmAccountDeletion": "confirmAccountDeletion":
MessageLookupByLibrary.simpleMessage("Bekreft sletting av konto"), MessageLookupByLibrary.simpleMessage("Bekreft sletting av konto"),
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
@ -57,12 +59,21 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage( MessageLookupByLibrary.simpleMessage(
"Edits to location will only be seen within Ente"), "Edits to location will only be seen within Ente"),
"email": MessageLookupByLibrary.simpleMessage("E-post"), "email": MessageLookupByLibrary.simpleMessage("E-post"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterValidEmail": MessageLookupByLibrary.simpleMessage( "enterValidEmail": MessageLookupByLibrary.simpleMessage(
"Vennligst skriv inn en gyldig e-postadresse."), "Vennligst skriv inn en gyldig e-postadresse."),
"enterYourEmailAddress": MessageLookupByLibrary.simpleMessage( "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage(
"Skriv inn e-postadressen din"), "Skriv inn e-postadressen din"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"), "feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"invalidEmailAddress": "invalidEmailAddress":
MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"), MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
@ -77,6 +88,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Modify your query, or try searching for"), "Modify your query, or try searching for"),
"moveToHiddenAlbum": "moveToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Move to hidden album"), MessageLookupByLibrary.simpleMessage("Move to hidden album"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"search": MessageLookupByLibrary.simpleMessage("Search"), "search": MessageLookupByLibrary.simpleMessage("Search"),
"selectALocation": "selectALocation":
MessageLookupByLibrary.simpleMessage("Select a location"), MessageLookupByLibrary.simpleMessage("Select a location"),

View file

@ -49,6 +49,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Zmień hasło"), MessageLookupByLibrary.simpleMessage("Zmień hasło"),
"checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage(
"Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację"), "Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
"Kod został skopiowany do schowka"), "Kod został skopiowany do schowka"),
"confirm": MessageLookupByLibrary.simpleMessage("Potwierdź"), "confirm": MessageLookupByLibrary.simpleMessage("Potwierdź"),
@ -101,6 +103,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych"), "Wprowadź nowe hasło, którego możemy użyć do zaszyfrowania Twoich danych"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Wprowadź hasło, którego możemy użyć do zaszyfrowania Twoich danych"), "Wprowadź hasło, którego możemy użyć do zaszyfrowania Twoich danych"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Enter person name"),
"enterValidEmail": MessageLookupByLibrary.simpleMessage( "enterValidEmail": MessageLookupByLibrary.simpleMessage(
"Podaj poprawny adres e-mail."), "Podaj poprawny adres e-mail."),
"enterYourEmailAddress": "enterYourEmailAddress":
@ -109,10 +113,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Wprowadź hasło"), MessageLookupByLibrary.simpleMessage("Wprowadź hasło"),
"enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage(
"Wprowadź swój klucz odzyskiwania"), "Wprowadź swój klucz odzyskiwania"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"feedback": MessageLookupByLibrary.simpleMessage("Informacja zwrotna"), "feedback": MessageLookupByLibrary.simpleMessage("Informacja zwrotna"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"), "fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Nie pamiętam hasła"), MessageLookupByLibrary.simpleMessage("Nie pamiętam hasła"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage( "generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage(
"Generowanie kluczy szyfrujących..."), "Generowanie kluczy szyfrujących..."),
"howItWorks": MessageLookupByLibrary.simpleMessage("Jak to działa"), "howItWorks": MessageLookupByLibrary.simpleMessage("Jak to działa"),
@ -122,6 +131,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"), MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"),
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
"Nieprawidłowy klucz odzyskiwania"), "Nieprawidłowy klucz odzyskiwania"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"invalidEmailAddress": "invalidEmailAddress":
MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"), MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"),
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
@ -166,6 +177,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Jeśli zapomnisz hasła, jedynym sposobem odzyskania danych jest ten klucz."), "Jeśli zapomnisz hasła, jedynym sposobem odzyskania danych jest ten klucz."),
"recoverySuccessful": "recoverySuccessful":
MessageLookupByLibrary.simpleMessage("Odzyskano pomyślnie!"), MessageLookupByLibrary.simpleMessage("Odzyskano pomyślnie!"),
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remove person label"),
"resendEmail": "resendEmail":
MessageLookupByLibrary.simpleMessage("Wyślij e-mail ponownie"), MessageLookupByLibrary.simpleMessage("Wyślij e-mail ponownie"),
"resetPasswordTitle": "resetPasswordTitle":

View file

@ -98,7 +98,7 @@ class MessageLookup extends MessageLookupByLibrary {
"${storageAmountInGB} GB cada vez que alguém se inscrever para um plano pago e aplica o seu código"; "${storageAmountInGB} GB cada vez que alguém se inscrever para um plano pago e aplica o seu código";
static String m25(freeAmount, storageUnit) => static String m25(freeAmount, storageUnit) =>
"${freeAmount} ${storageUnit} grátis"; "${freeAmount} ${storageUnit} livre";
static String m26(endDate) => "Teste gratuito acaba em ${endDate}"; static String m26(endDate) => "Teste gratuito acaba em ${endDate}";
@ -225,6 +225,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são <underline>criptografados de ponta a ponta</underline>."), "Eu entendo que se eu perder minha senha, posso perder meus dados, já que meus dados são <underline>criptografados de ponta a ponta</underline>."),
"activeSessions": "activeSessions":
MessageLookupByLibrary.simpleMessage("Sessões ativas"), MessageLookupByLibrary.simpleMessage("Sessões ativas"),
"addAName": MessageLookupByLibrary.simpleMessage("Adicione um nome"),
"addANewEmail": "addANewEmail":
MessageLookupByLibrary.simpleMessage("Adicionar um novo email"), MessageLookupByLibrary.simpleMessage("Adicionar um novo email"),
"addCollaborator": "addCollaborator":
@ -445,6 +446,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Agrupar por tempo de captura"), "Agrupar por tempo de captura"),
"clubByFileName": MessageLookupByLibrary.simpleMessage( "clubByFileName": MessageLookupByLibrary.simpleMessage(
"Agrupar pelo nome de arquivo"), "Agrupar pelo nome de arquivo"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Progresso de agrupamento"),
"codeAppliedPageTitle": "codeAppliedPageTitle":
MessageLookupByLibrary.simpleMessage("Código aplicado"), MessageLookupByLibrary.simpleMessage("Código aplicado"),
"codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage(
@ -587,7 +590,7 @@ class MessageLookup extends MessageLookupByLibrary {
"descriptions": MessageLookupByLibrary.simpleMessage("Descrições"), "descriptions": MessageLookupByLibrary.simpleMessage("Descrições"),
"deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"), "deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"),
"designedToOutlive": "designedToOutlive":
MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"), MessageLookupByLibrary.simpleMessage("Feito para ter longevidade"),
"details": MessageLookupByLibrary.simpleMessage("Detalhes"), "details": MessageLookupByLibrary.simpleMessage("Detalhes"),
"devAccountChanged": MessageLookupByLibrary.simpleMessage( "devAccountChanged": MessageLookupByLibrary.simpleMessage(
"A conta de desenvolvedor que usamos para publicar o Ente na App Store foi alterada. Por esse motivo, você precisará fazer entrar novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável."), "A conta de desenvolvedor que usamos para publicar o Ente na App Store foi alterada. Por esse motivo, você precisará fazer entrar novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável."),
@ -690,6 +693,8 @@ class MessageLookup extends MessageLookupByLibrary {
"enterPassword": MessageLookupByLibrary.simpleMessage("Digite a senha"), "enterPassword": MessageLookupByLibrary.simpleMessage("Digite a senha"),
"enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage(
"Insira a senha para criptografar seus dados"), "Insira a senha para criptografar seus dados"),
"enterPersonName":
MessageLookupByLibrary.simpleMessage("Inserir nome da pessoa"),
"enterReferralCode": MessageLookupByLibrary.simpleMessage( "enterReferralCode": MessageLookupByLibrary.simpleMessage(
"Insira o código de referência"), "Insira o código de referência"),
"enterThe6digitCodeFromnyourAuthenticatorApp": "enterThe6digitCodeFromnyourAuthenticatorApp":
@ -714,6 +719,10 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Exportar logs"), "exportLogs": MessageLookupByLibrary.simpleMessage("Exportar logs"),
"exportYourData": "exportYourData":
MessageLookupByLibrary.simpleMessage("Exportar seus dados"), MessageLookupByLibrary.simpleMessage("Exportar seus dados"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Reconhecimento facial"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados."),
"faces": MessageLookupByLibrary.simpleMessage("Rostos"), "faces": MessageLookupByLibrary.simpleMessage("Rostos"),
"failedToApplyCode": "failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"), MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"),
@ -755,11 +764,15 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Arquivos excluídos"), MessageLookupByLibrary.simpleMessage("Arquivos excluídos"),
"filesSavedToGallery": "filesSavedToGallery":
MessageLookupByLibrary.simpleMessage("Arquivos salvos na galeria"), MessageLookupByLibrary.simpleMessage("Arquivos salvos na galeria"),
"findPeopleByName": MessageLookupByLibrary.simpleMessage(
"Encontre pessoas rapidamente por nome"),
"flip": MessageLookupByLibrary.simpleMessage("Inverter"), "flip": MessageLookupByLibrary.simpleMessage("Inverter"),
"forYourMemories": "forYourMemories":
MessageLookupByLibrary.simpleMessage("para suas memórias"), MessageLookupByLibrary.simpleMessage("para suas memórias"),
"forgotPassword": "forgotPassword":
MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"), MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"),
"foundFaces":
MessageLookupByLibrary.simpleMessage("Rostos encontrados"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage( "freeStorageClaimed": MessageLookupByLibrary.simpleMessage(
"Armazenamento gratuito reivindicado"), "Armazenamento gratuito reivindicado"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
@ -823,6 +836,8 @@ class MessageLookup extends MessageLookupByLibrary {
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
"Chave de recuperação incorreta"), "Chave de recuperação incorreta"),
"indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"), "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": "insecureDevice":
MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"), MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"),
"installManually": "installManually":
@ -1057,6 +1072,7 @@ class MessageLookup extends MessageLookupByLibrary {
"pendingItems": MessageLookupByLibrary.simpleMessage("Itens pendentes"), "pendingItems": MessageLookupByLibrary.simpleMessage("Itens pendentes"),
"pendingSync": "pendingSync":
MessageLookupByLibrary.simpleMessage("Sincronização pendente"), MessageLookupByLibrary.simpleMessage("Sincronização pendente"),
"people": MessageLookupByLibrary.simpleMessage("Pessoas"),
"peopleUsingYourCode": "peopleUsingYourCode":
MessageLookupByLibrary.simpleMessage("Pessoas que usam seu código"), MessageLookupByLibrary.simpleMessage("Pessoas que usam seu código"),
"permDeleteWarning": MessageLookupByLibrary.simpleMessage( "permDeleteWarning": MessageLookupByLibrary.simpleMessage(
@ -1190,6 +1206,8 @@ class MessageLookup extends MessageLookupByLibrary {
"removeParticipant": "removeParticipant":
MessageLookupByLibrary.simpleMessage("Remover participante"), MessageLookupByLibrary.simpleMessage("Remover participante"),
"removeParticipantBody": m43, "removeParticipantBody": m43,
"removePersonLabel":
MessageLookupByLibrary.simpleMessage("Remover etiqueta da pessoa"),
"removePublicLink": "removePublicLink":
MessageLookupByLibrary.simpleMessage("Remover link público"), MessageLookupByLibrary.simpleMessage("Remover link público"),
"removeShareItemsWarning": MessageLookupByLibrary.simpleMessage( "removeShareItemsWarning": MessageLookupByLibrary.simpleMessage(
@ -1253,7 +1271,7 @@ class MessageLookup extends MessageLookupByLibrary {
"searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
"Pesquisar por data, mês ou ano"), "Pesquisar por data, mês ou ano"),
"searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
"Encontre todas as fotos de uma pessoa"), "Pessoas serão exibidas aqui uma vez que a indexação é feita"),
"searchFileTypesAndNamesEmptySection": "searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("Tipos de arquivo e nomes"), MessageLookupByLibrary.simpleMessage("Tipos de arquivo e nomes"),
"searchHint1": MessageLookupByLibrary.simpleMessage( "searchHint1": MessageLookupByLibrary.simpleMessage(

View file

@ -382,6 +382,8 @@ class MessageLookup extends MessageLookupByLibrary {
"close": MessageLookupByLibrary.simpleMessage("关闭"), "close": MessageLookupByLibrary.simpleMessage("关闭"),
"clubByCaptureTime": MessageLookupByLibrary.simpleMessage("按拍摄时间分组"), "clubByCaptureTime": MessageLookupByLibrary.simpleMessage("按拍摄时间分组"),
"clubByFileName": MessageLookupByLibrary.simpleMessage("按文件名排序"), "clubByFileName": MessageLookupByLibrary.simpleMessage("按文件名排序"),
"clusteringProgress":
MessageLookupByLibrary.simpleMessage("Clustering progress"),
"codeAppliedPageTitle": MessageLookupByLibrary.simpleMessage("代码已应用"), "codeAppliedPageTitle": MessageLookupByLibrary.simpleMessage("代码已应用"),
"codeCopiedToClipboard": "codeCopiedToClipboard":
MessageLookupByLibrary.simpleMessage("代码已复制到剪贴板"), MessageLookupByLibrary.simpleMessage("代码已复制到剪贴板"),
@ -543,7 +545,7 @@ class MessageLookup extends MessageLookupByLibrary {
"emailVerificationToggle": "emailVerificationToggle":
MessageLookupByLibrary.simpleMessage("电子邮件验证"), MessageLookupByLibrary.simpleMessage("电子邮件验证"),
"emailYourLogs": MessageLookupByLibrary.simpleMessage("通过电子邮件发送您的日志"), "emailYourLogs": MessageLookupByLibrary.simpleMessage("通过电子邮件发送您的日志"),
"empty": MessageLookupByLibrary.simpleMessage(""), "empty": MessageLookupByLibrary.simpleMessage(""),
"emptyTrash": MessageLookupByLibrary.simpleMessage("要清空回收站吗?"), "emptyTrash": MessageLookupByLibrary.simpleMessage("要清空回收站吗?"),
"enableMaps": MessageLookupByLibrary.simpleMessage("启用地图"), "enableMaps": MessageLookupByLibrary.simpleMessage("启用地图"),
"enableMapsDesc": MessageLookupByLibrary.simpleMessage( "enableMapsDesc": MessageLookupByLibrary.simpleMessage(
@ -592,6 +594,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("此链接已过期。请选择新的过期时间或禁用链接有效期。"), MessageLookupByLibrary.simpleMessage("此链接已过期。请选择新的过期时间或禁用链接有效期。"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"), "exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"), "exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"),
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faceRecognitionIndexingDescription": MessageLookupByLibrary.simpleMessage(
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
"faces": MessageLookupByLibrary.simpleMessage("人脸"), "faces": MessageLookupByLibrary.simpleMessage("人脸"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法使用此代码"), "failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法使用此代码"),
"failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"), "failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"),
@ -626,6 +632,7 @@ class MessageLookup extends MessageLookupByLibrary {
"flip": MessageLookupByLibrary.simpleMessage("上下翻转"), "flip": MessageLookupByLibrary.simpleMessage("上下翻转"),
"forYourMemories": MessageLookupByLibrary.simpleMessage("为您的回忆"), "forYourMemories": MessageLookupByLibrary.simpleMessage("为您的回忆"),
"forgotPassword": MessageLookupByLibrary.simpleMessage("忘记密码"), "forgotPassword": MessageLookupByLibrary.simpleMessage("忘记密码"),
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
"freeStorageClaimed": MessageLookupByLibrary.simpleMessage("已领取的免费存储"), "freeStorageClaimed": MessageLookupByLibrary.simpleMessage("已领取的免费存储"),
"freeStorageOnReferralSuccess": m24, "freeStorageOnReferralSuccess": m24,
"freeStorageSpace": m25, "freeStorageSpace": m25,
@ -679,6 +686,8 @@ class MessageLookup extends MessageLookupByLibrary {
"incorrectRecoveryKeyTitle": "incorrectRecoveryKeyTitle":
MessageLookupByLibrary.simpleMessage("不正确的恢复密钥"), MessageLookupByLibrary.simpleMessage("不正确的恢复密钥"),
"indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"), "indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused, will automatically resume when device is ready"),
"insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"), "insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"),
"installManually": MessageLookupByLibrary.simpleMessage("手动安装"), "installManually": MessageLookupByLibrary.simpleMessage("手动安装"),
"invalidEmailAddress": "invalidEmailAddress":

View file

@ -4034,10 +4034,10 @@ class S {
); );
} }
/// `Free trial valid till {endDate}.\nYou can purchase a paid plan afterwards.` /// `Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.`
String playStoreFreeTrialValidTill(Object endDate) { String playStoreFreeTrialValidTill(Object endDate) {
return Intl.message( return Intl.message(
'Free trial valid till $endDate.\nYou can purchase a paid plan afterwards.', 'Free trial valid till $endDate.\nYou can choose a paid plan afterwards.',
name: 'playStoreFreeTrialValidTill', name: 'playStoreFreeTrialValidTill',
desc: '', desc: '',
args: [endDate], args: [endDate],
@ -6969,10 +6969,10 @@ class S {
); );
} }
/// `Find all photos of a person` /// `Persons will be shown here once indexing is done`
String get searchFaceEmptySection { String get searchFaceEmptySection {
return Intl.message( return Intl.message(
'Find all photos of a person', 'Persons will be shown here once indexing is done',
name: 'searchFaceEmptySection', name: 'searchFaceEmptySection',
desc: '', desc: '',
args: [], args: [],
@ -8168,6 +8168,16 @@ class S {
); );
} }
/// `People`
String get people {
return Intl.message(
'People',
name: 'people',
desc: '',
args: [],
);
}
/// `Contents` /// `Contents`
String get contents { String get contents {
return Intl.message( return Intl.message(
@ -8388,26 +8398,6 @@ class S {
); );
} }
/// `Auto pair`
String get autoPair {
return Intl.message(
'Auto pair',
name: 'autoPair',
desc: '',
args: [],
);
}
/// `Pair with PIN`
String get pairWithPin {
return Intl.message(
'Pair with PIN',
name: 'pairWithPin',
desc: '',
args: [],
);
}
/// `Device not found` /// `Device not found`
String get deviceNotFound { String get deviceNotFound {
return Intl.message( return Intl.message(
@ -8468,6 +8458,26 @@ class S {
); );
} }
/// `Add a name`
String get addAName {
return Intl.message(
'Add a name',
name: 'addAName',
desc: '',
args: [],
);
}
/// `Find people quickly by name`
String get findPeopleByName {
return Intl.message(
'Find people quickly by name',
name: 'findPeopleByName',
desc: '',
args: [],
);
}
/// `{count, plural, zero {Add viewer} one {Add viewer} other {Add viewers}}` /// `{count, plural, zero {Add viewer} one {Add viewer} other {Add viewers}}`
String addViewers(num count) { String addViewers(num count) {
return Intl.plural( return Intl.plural(
@ -8594,6 +8604,26 @@ class S {
); );
} }
/// `Enter person name`
String get enterPersonName {
return Intl.message(
'Enter person name',
name: 'enterPersonName',
desc: '',
args: [],
);
}
/// `Remove person label`
String get removePersonLabel {
return Intl.message(
'Remove person label',
name: 'removePersonLabel',
desc: '',
args: [],
);
}
/// `Auto pair works only with devices that support Chromecast.` /// `Auto pair works only with devices that support Chromecast.`
String get autoPairDesc { String get autoPairDesc {
return Intl.message( return Intl.message(
@ -8703,6 +8733,76 @@ class S {
args: [], args: [],
); );
} }
/// `Auto pair`
String get autoPair {
return Intl.message(
'Auto pair',
name: 'autoPair',
desc: '',
args: [],
);
}
/// `Pair with PIN`
String get pairWithPin {
return Intl.message(
'Pair with PIN',
name: 'pairWithPin',
desc: '',
args: [],
);
}
/// `Face recognition`
String get faceRecognition {
return Intl.message(
'Face recognition',
name: 'faceRecognition',
desc: '',
args: [],
);
}
/// `Please note that this will result in a higher bandwidth and battery usage until all items are indexed.`
String get faceRecognitionIndexingDescription {
return Intl.message(
'Please note that this will result in a higher bandwidth and battery usage until all items are indexed.',
name: 'faceRecognitionIndexingDescription',
desc: '',
args: [],
);
}
/// `Found faces`
String get foundFaces {
return Intl.message(
'Found faces',
name: 'foundFaces',
desc: '',
args: [],
);
}
/// `Clustering progress`
String get clusteringProgress {
return Intl.message(
'Clustering progress',
name: 'clusteringProgress',
desc: '',
args: [],
);
}
/// `Indexing is paused, will automatically resume when device is ready`
String get indexingIsPaused {
return Intl.message(
'Indexing is paused, will automatically resume when device is ready',
name: 'indexingIsPaused',
desc: '',
args: [],
);
}
} }
class AppLocalizationDelegate extends LocalizationsDelegate<S> { class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View file

@ -0,0 +1,111 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// CenterBox is a box where x,y is the center of the box
class CenterBox extends $pb.GeneratedMessage {
factory CenterBox({
$core.double? x,
$core.double? y,
$core.double? height,
$core.double? width,
}) {
final $result = create();
if (x != null) {
$result.x = x;
}
if (y != null) {
$result.y = y;
}
if (height != null) {
$result.height = height;
}
if (width != null) {
$result.width = width;
}
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);
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
;
@$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;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static CenterBox create() => CenterBox._();
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? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
set y($core.double v) { $_setFloat(1, v); }
@$pb.TagNumber(2)
$core.bool hasY() => $_has(1);
@$pb.TagNumber(2)
void clearY() => clearField(2);
@$pb.TagNumber(3)
$core.double get height => $_getN(2);
@$pb.TagNumber(3)
set height($core.double v) { $_setFloat(2, v); }
@$pb.TagNumber(3)
$core.bool hasHeight() => $_has(2);
@$pb.TagNumber(3)
void clearHeight() => clearField(3);
@$pb.TagNumber(4)
$core.double get width => $_getN(3);
@$pb.TagNumber(4)
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');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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

View file

@ -0,0 +1,38 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use centerBoxDescriptor instead')
const CenterBox$json = {
'1': 'CenterBox',
'2': [
{'1': 'x', '3': 1, '4': 1, '5': 2, '9': 0, '10': 'x', '17': true},
{'1': 'y', '3': 2, '4': 1, '5': 2, '9': 1, '10': 'y', '17': true},
{'1': 'height', '3': 3, '4': 1, '5': 2, '9': 2, '10': 'height', '17': true},
{'1': 'width', '3': 4, '4': 1, '5': 2, '9': 3, '10': 'width', '17': true},
],
'8': [
{'1': '_x'},
{'1': '_y'},
{'1': '_height'},
{'1': '_width'},
],
};
/// Descriptor for `CenterBox`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List centerBoxDescriptor = $convert.base64Decode(
'CglDZW50ZXJCb3gSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBARIbCgZoZW'
'lnaHQYAyABKAJIAlIGaGVpZ2h0iAEBEhkKBXdpZHRoGAQgASgCSANSBXdpZHRoiAEBQgQKAl94'
'QgQKAl95QgkKB19oZWlnaHRCCAoGX3dpZHRo');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/box.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'box.pb.dart';

View file

@ -0,0 +1,83 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// EPoint is a point in 2D space
class EPoint extends $pb.GeneratedMessage {
factory EPoint({
$core.double? x,
$core.double? y,
}) {
final $result = create();
if (x != null) {
$result.x = x;
}
if (y != null) {
$result.y = y;
}
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);
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
;
@$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;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EPoint create() => EPoint._();
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? _defaultInstance;
@$pb.TagNumber(1)
$core.double get x => $_getN(0);
@$pb.TagNumber(1)
set x($core.double v) { $_setFloat(0, v); }
@$pb.TagNumber(1)
$core.bool hasX() => $_has(0);
@$pb.TagNumber(1)
void clearX() => clearField(1);
@$pb.TagNumber(2)
$core.double get y => $_getN(1);
@$pb.TagNumber(2)
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');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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

View file

@ -0,0 +1,33 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use ePointDescriptor instead')
const EPoint$json = {
'1': 'EPoint',
'2': [
{'1': 'x', '3': 1, '4': 1, '5': 2, '9': 0, '10': 'x', '17': true},
{'1': 'y', '3': 2, '4': 1, '5': 2, '9': 1, '10': 'y', '17': true},
],
'8': [
{'1': '_x'},
{'1': '_y'},
],
};
/// Descriptor for `EPoint`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List ePointDescriptor = $convert.base64Decode(
'CgZFUG9pbnQSEQoBeBgBIAEoAkgAUgF4iAEBEhEKAXkYAiABKAJIAVIBeYgBAUIECgJfeEIECg'
'JfeQ==');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/point.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'point.pb.dart';

View file

@ -0,0 +1,64 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// Vector is generic message for dealing with lists of doubles
/// It should ideally be used independently and not as a submessage
class EVector extends $pb.GeneratedMessage {
factory EVector({
$core.Iterable<$core.double>? values,
}) {
final $result = create();
if (values != null) {
$result.values.addAll(values);
}
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);
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
;
@$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;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static EVector create() => EVector._();
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? _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');

View file

@ -0,0 +1,11 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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

View file

@ -0,0 +1,27 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use eVectorDescriptor instead')
const EVector$json = {
'1': 'EVector',
'2': [
{'1': 'values', '3': 1, '4': 3, '5': 1, '10': 'values'},
],
};
/// Descriptor for `EVector`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List eVectorDescriptor = $convert.base64Decode(
'CgdFVmVjdG9yEhYKBnZhbHVlcxgBIAMoAVIGdmFsdWVz');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: ente/common/vector.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'vector.pb.dart';

View file

@ -0,0 +1,169 @@
//
// Generated code. Do not modify.
// source: ente/ml/face.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// 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
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
import '../common/box.pb.dart' as $0;
import '../common/point.pb.dart' as $1;
class Detection extends $pb.GeneratedMessage {
factory Detection({
$0.CenterBox? box,
$1.EPoint? landmarks,
}) {
final $result = create();
if (box != null) {
$result.box = box;
}
if (landmarks != null) {
$result.landmarks = landmarks;
}
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);
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')
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;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Detection create() => Detection._();
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? _defaultInstance;
@$pb.TagNumber(1)
$0.CenterBox get box => $_getN(0);
@$pb.TagNumber(1)
set box($0.CenterBox v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasBox() => $_has(0);
@$pb.TagNumber(1)
void clearBox() => clearField(1);
@$pb.TagNumber(1)
$0.CenterBox ensureBox() => $_ensure(0);
@$pb.TagNumber(2)
$1.EPoint get landmarks => $_getN(1);
@$pb.TagNumber(2)
set landmarks($1.EPoint v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasLandmarks() => $_has(1);
@$pb.TagNumber(2)
void clearLandmarks() => clearField(2);
@$pb.TagNumber(2)
$1.EPoint ensureLandmarks() => $_ensure(1);
}
class Face extends $pb.GeneratedMessage {
factory Face({
$core.String? id,
Detection? detection,
$core.double? confidence,
}) {
final $result = create();
if (id != null) {
$result.id = id;
}
if (detection != null) {
$result.detection = detection;
}
if (confidence != null) {
$result.confidence = confidence;
}
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);
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
;
@$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;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Face create() => Face._();
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? _defaultInstance;
@$pb.TagNumber(1)
$core.String get id => $_getSZ(0);
@$pb.TagNumber(1)
set id($core.String v) { $_setString(0, v); }
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearId() => clearField(1);
@$pb.TagNumber(2)
Detection get detection => $_getN(1);
@$pb.TagNumber(2)
set detection(Detection v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasDetection() => $_has(1);
@$pb.TagNumber(2)
void clearDetection() => clearField(2);
@$pb.TagNumber(2)
Detection ensureDetection() => $_ensure(1);
@$pb.TagNumber(3)
$core.double get confidence => $_getN(2);
@$pb.TagNumber(3)
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');

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