diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml index 493185b6b..3a43924a3 100644 --- a/.github/workflows/mobile-lint.yml +++ b/.github/workflows/mobile-lint.yml @@ -9,7 +9,7 @@ on: - ".github/workflows/mobile-lint.yml" env: - FLUTTER_VERSION: "3.19.5" + FLUTTER_VERSION: "3.22.0" jobs: lint: diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index 1c8223c87..c11fb1121 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -106,7 +106,7 @@ const handleRead = async (path: string) => { res.headers.set("Content-Length", `${fileSize}`); // 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}`); } return res; @@ -132,6 +132,13 @@ const handleReadZip = async (zipPath: string, entryName: string) => { // Close the zip handle when the underlying stream closes. 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, { headers: { // 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. "Content-Type": "application/octet-stream", "Content-Length": `${entry.size}`, - // 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 - "X-Last-Modified-Ms": `${entry.time}`, + "X-Last-Modified-Ms": `${modifiedMs}`, }, }); }; diff --git a/mobile/README.md b/mobile/README.md index fc17f6b26..6d86ad534 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -46,7 +46,7 @@ You can alternatively install the build from PlayStore or F-Droid. ## 🧑💻 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` diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 558a27910..9f74d552a 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -427,7 +427,7 @@ SPEC CHECKSUMS: home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 in_app_purchase_storekit: 0e4b3c2e43ba1e1281f4f46dd71b0593ce529892 - integration_test: 13825b8a9334a850581300559b8839134b124670 + integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009 local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98 local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/lib/l10n/intl_pt.arb index f47dd89e9..10117b426 100644 --- a/mobile/lib/l10n/intl_pt.arb +++ b/mobile/lib/l10n/intl_pt.arb @@ -987,7 +987,7 @@ "fileTypesAndNames": "Tipos de arquivo e nomes", "location": "Local", "moments": "Momentos", - "searchFaceEmptySection": "Encontre todas as fotos de uma pessoa", + "searchFaceEmptySection": "Pessoas serão exibidas aqui uma vez que a indexação é feita", "searchDatesEmptySection": "Pesquisar por data, mês ou ano", "searchLocationEmptySection": "Fotos de grupo que estão sendo tiradas em algum raio da foto", "searchPeopleEmptySection": "Convide pessoas e você verá todas as fotos compartilhadas por elas aqui", @@ -1042,7 +1042,7 @@ "@storageUsageInfo": { "description": "Example: 1.2 GB of 2 GB used or 100 GB or 2TB used" }, - "freeStorageSpace": "{freeAmount} {storageUnit} grátis", + "freeStorageSpace": "{freeAmount} {storageUnit} livre", "appVersion": "Versão: {versionValue}", "verifyIDLabel": "Verificar", "fileInfoAddDescHint": "Adicionar descrição...", @@ -1171,6 +1171,7 @@ } }, "faces": "Rostos", + "people": "Pessoas", "contents": "Conteúdos", "addNew": "Adicionar novo", "@addNew": { @@ -1196,14 +1197,14 @@ "verifyPasskey": "Verificar chave de acesso", "playOnTv": "Reproduzir álbum na TV", "pair": "Parear", - "autoPair": "Pareamento automático", - "pairWithPin": "Parear com PIN", "deviceNotFound": "Dispositivo não encontrado", "castInstruction": "Visite cast.ente.io no dispositivo que você deseja parear.\n\ndigite o código abaixo para reproduzir o álbum em sua TV.", "deviceCodeHint": "Insira o código", "joinDiscord": "Junte-se ao Discord", "locations": "Locais", "descriptions": "Descrições", + "addAName": "Adicione um nome", + "findPeopleByName": "Encontre pessoas rapidamente por nome", "addViewers": "{count, plural, zero {Adicionar visualizador} one {Adicionar visualizador} other {Adicionar Visualizadores}}", "addCollaborators": "{count, plural, zero {Adicionar colaborador} one {Adicionar coloborador} other {Adicionar colaboradores}}", "longPressAnEmailToVerifyEndToEndEncryption": "Pressione e segure um e-mail para verificar a criptografia de ponta a ponta.", @@ -1216,6 +1217,8 @@ "customEndpoint": "Conectado a {endpoint}", "createCollaborativeLink": "Criar link colaborativo", "search": "Pesquisar", + "enterPersonName": "Inserir nome da pessoa", + "removePersonLabel": "Remover etiqueta da pessoa", "autoPairDesc": "O pareamento automático funciona apenas com dispositivos que suportam o Chromecast.", "manualPairDesc": "Parear com o PIN funciona com qualquer tela que você deseja ver o seu álbum ativado.", "connectToDevice": "Conectar ao dispositivo", @@ -1227,8 +1230,10 @@ "castIPMismatchTitle": "Falha ao transmitir álbum", "castIPMismatchBody": "Certifique-se de estar na mesma rede que a TV.", "pairingComplete": "Pareamento concluído", - "faceRecognition": "Face recognition", - "faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.", - "foundFaces": "Found faces", - "clusteringProgress": "Clustering progress" + "autoPair": "Pareamento automático", + "pairWithPin": "Parear com PIN", + "faceRecognition": "Reconhecimento facial", + "faceRecognitionIndexingDescription": "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados.", + "foundFaces": "Rostos encontrados", + "clusteringProgress": "Progresso de agrupamento" } \ No newline at end of file diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 1d1082bfd..8b71025e9 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: "direct main" description: name: animated_list_plus - sha256: fe66f9c300d715254727fbdf050487844d17b013fec344fa28081d29bddbdf1a + sha256: fb3d7f1fbaf5af84907f3c739236bacda8bf32cbe1f118dd51510752883ff50c url: "https://pub.dev" source: hosted - version: "0.4.5" + version: "0.5.2" animated_stack_widget: dependency: transitive description: @@ -971,10 +971,10 @@ packages: dependency: "direct main" description: name: home_widget - sha256: "29565bfee4b32eaf9e7e8b998d504618b779a74b2b1ac62dd4dac7468e66f1a3" + sha256: "2a0fdd6267ff975bd07bedf74686bd5577200f504f5de36527ac1b56bdbe68e3" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.6.0" html: dependency: transitive description: @@ -1152,26 +1152,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.4" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.3" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" like_button: dependency: "direct main" description: @@ -1368,10 +1368,10 @@ packages: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.0" mgrs_dart: dependency: transitive description: @@ -2144,10 +2144,10 @@ packages: dependency: "direct main" description: name: styled_text - sha256: f72928d1ebe8cb149e3b34a689cb1ddca696b808187cf40ac3a0bd183dff379c + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "8.1.0" sync_http: dependency: transitive description: @@ -2160,18 +2160,18 @@ packages: dependency: "direct main" description: name: syncfusion_flutter_core - sha256: "9be1bb9bbdb42823439a18da71484f1964c14dbe1c255ab1b931932b12fa96e8" + sha256: "63108a33f9b0d89f7b6b56cce908b8e519fe433dbbe0efcf41ad3e8bb2081bd9" url: "https://pub.dev" source: hosted - version: "19.4.56" + version: "25.2.5" syncfusion_flutter_sliders: dependency: "direct main" description: name: syncfusion_flutter_sliders - sha256: "1f6a63ccab4180b544074b9264a20f01ee80b553de154192fe1d7b434089d3c2" + sha256: f27310bedc0e96e84054f0a70ac593d1a3c38397c158c5226ba86027ad77b2c1 url: "https://pub.dev" source: hosted - version: "19.4.56" + version: "25.2.5" synchronized: dependency: "direct main" description: @@ -2192,26 +2192,26 @@ packages: dependency: "direct dev" description: name: test - sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f + sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" url: "https://pub.dev" source: hosted - version: "1.24.9" + version: "1.25.2" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" test_core: dependency: transitive description: name: test_core - sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a + sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" url: "https://pub.dev" source: hosted - version: "0.5.9" + version: "0.6.0" timezone: dependency: transitive description: @@ -2441,10 +2441,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.1" volume_controller: dependency: transitive description: @@ -2591,4 +2591,4 @@ packages: version: "3.1.2" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.20.0-1.2.pre" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index f864ef705..538898cf9 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -21,7 +21,7 @@ environment: dependencies: adaptive_theme: ^3.1.0 animate_do: ^2.0.0 - animated_list_plus: ^0.4.5 + animated_list_plus: ^0.5.2 archive: ^3.1.2 background_fetch: ^1.2.1 battery_info: ^1.1.1 @@ -93,13 +93,13 @@ dependencies: fluttertoast: ^8.0.6 freezed_annotation: ^2.4.1 google_nav_bar: ^5.0.5 - home_widget: ^0.5.0 + home_widget: ^0.6.0 html_unescape: ^2.0.0 http: ^1.1.0 image: ^4.0.17 image_editor: ^1.3.0 in_app_purchase: ^3.0.7 - intl: ^0.18.0 + intl: ^0.19.0 json_annotation: ^4.8.0 latlong2: ^0.9.0 like_button: ^2.0.5 @@ -152,9 +152,9 @@ dependencies: sqlite3_flutter_libs: ^0.5.20 sqlite_async: ^0.6.1 step_progress_indicator: ^1.0.2 - styled_text: ^7.0.0 - syncfusion_flutter_core: ^19.2.49 - syncfusion_flutter_sliders: ^19.2.49 + styled_text: ^8.1.0 + syncfusion_flutter_core: ^25.2.5 + syncfusion_flutter_sliders: ^25.2.5 synchronized: ^3.1.0 tuple: ^2.0.0 uni_links: ^0.5.1 @@ -177,6 +177,7 @@ dependency_overrides: # Remove this after removing dependency from flutter_sodium. # Newer flutter packages depends on ffi > 2.0.0 while flutter_sodium depends on ffi < 2.0.0 ffi: 2.1.0 + intl: 0.18.1 video_player: git: url: https://github.com/ente-io/packages.git diff --git a/web/apps/auth/src/components/AuthFooter.tsx b/web/apps/auth/src/components/AuthFooter.tsx deleted file mode 100644 index 029103125..000000000 --- a/web/apps/auth/src/components/AuthFooter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { Button } from "@mui/material"; -import { t } from "i18next"; - -export const AuthFooter = () => { - return ( -
{t("AUTH_DOWNLOAD_MOBILE_APP")}
- - - -- {issuer} -
-- {account} -
-- {code} -
-- {t("AUTH_NEXT")} -
-- {nextCode} -
-{t("NO_RESULTS")}
+ ) : ( + + )} ++ {code.issuer} +
++ {code.account} +
++ {otp} +
++ {t("AUTH_NEXT")} +
++ {nextOTP} +
+{t("AUTH_DOWNLOAD_MOBILE_APP")}
+ + + +{t("NO_RESULTS")}
- ) : ( - - )} - => {
const masterKey = await getActualKey();
try {
@@ -33,7 +33,7 @@ export const getAuthCodes = async (): Promise => {
entity.header,
authenticatorKey,
);
- return Code.fromRawData(entity.id, decryptedCode);
+ return codeFromURIString(entity.id, decryptedCode);
} catch (e) {
log.error(`failed to parse codeId = ${entity.id}`);
return null;
@@ -65,6 +65,20 @@ export const getAuthCodes = async (): Promise => {
}
};
+interface AuthEntity {
+ id: string;
+ encryptedData: string | null;
+ header: string | null;
+ isDeleted: boolean;
+ createdAt: number;
+ updatedAt: number;
+}
+
+interface AuthKey {
+ encryptedKey: string;
+ header: string;
+}
+
export const getAuthKey = async (): Promise => {
try {
const resp = await HTTPService.get(
diff --git a/web/apps/auth/src/types/api.ts b/web/apps/auth/src/types/api.ts
deleted file mode 100644
index 569df8185..000000000
--- a/web/apps/auth/src/types/api.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export interface AuthEntity {
- id: string;
- encryptedData: string | null;
- header: string | null;
- isDeleted: boolean;
- createdAt: number;
- updatedAt: number;
-}
-
-export interface AuthKey {
- encryptedKey: string;
- header: string;
-}
diff --git a/web/apps/auth/src/types/code.ts b/web/apps/auth/src/types/code.ts
deleted file mode 100644
index d61a2dcd6..000000000
--- a/web/apps/auth/src/types/code.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { URI } from "vscode-uri";
-
-type Type = "totp" | "TOTP" | "hotp" | "HOTP";
-
-type AlgorithmType =
- | "sha1"
- | "SHA1"
- | "sha256"
- | "SHA256"
- | "sha512"
- | "SHA512";
-
-export class Code {
- static readonly defaultDigits = 6;
- static readonly defaultAlgo = "sha1";
- static readonly defaultPeriod = 30;
-
- // id for the corresponding auth entity
- id?: String;
- account: string;
- issuer: string;
- digits?: number;
- period: number;
- secret: string;
- algorithm: AlgorithmType;
- type: Type;
- rawData?: string;
-
- constructor(
- account: string,
- issuer: string,
- digits: number | undefined,
- period: number,
- secret: string,
- algorithm: AlgorithmType,
- type: Type,
- rawData?: string,
- id?: string,
- ) {
- this.account = account;
- this.issuer = issuer;
- this.digits = digits;
- this.period = period;
- this.secret = secret;
- this.algorithm = algorithm;
- this.type = type;
- this.rawData = rawData;
- this.id = id;
- }
-
- static fromRawData(id: string, rawData: string): Code {
- let santizedRawData = rawData
- .replace(/\+/g, "%2B")
- .replace(/:/g, "%3A")
- .replaceAll("\r", "");
- if (santizedRawData.startsWith('"')) {
- santizedRawData = santizedRawData.substring(1);
- }
- if (santizedRawData.endsWith('"')) {
- santizedRawData = santizedRawData.substring(
- 0,
- santizedRawData.length - 1,
- );
- }
-
- const uriParams = {};
- const searchParamsString =
- decodeURIComponent(santizedRawData).split("?")[1];
- searchParamsString.split("&").forEach((pair) => {
- const [key, value] = pair.split("=");
- uriParams[key] = value;
- });
-
- const uri = URI.parse(santizedRawData);
- let uriPath = decodeURIComponent(uri.path);
- if (
- uriPath.startsWith("/otpauth://") ||
- uriPath.startsWith("otpauth://")
- ) {
- uriPath = uriPath.split("otpauth://")[1];
- } else if (uriPath.startsWith("otpauth%3A//")) {
- uriPath = uriPath.split("otpauth%3A//")[1];
- }
-
- return new Code(
- Code._getAccount(uriPath),
- Code._getIssuer(uriPath, uriParams),
- Code._getDigits(uriParams),
- Code._getPeriod(uriParams),
- Code.getSanitizedSecret(uriParams),
- Code._getAlgorithm(uriParams),
- Code._getType(uriPath),
- rawData,
- id,
- );
- }
-
- private static _getAccount(uriPath: string): string {
- try {
- const path = decodeURIComponent(uriPath);
- if (path.includes(":")) {
- return path.split(":")[1];
- } else if (path.includes("/")) {
- return path.split("/")[1];
- }
- } catch (e) {
- return "";
- }
- }
-
- private static _getIssuer(
- uriPath: string,
- uriParams: { get?: any },
- ): string {
- try {
- if (uriParams["issuer"] !== undefined) {
- let issuer = uriParams["issuer"];
- // This is to handle bug in the ente auth app
- if (issuer.endsWith("period")) {
- issuer = issuer.substring(0, issuer.length - 6);
- }
- return issuer;
- }
- let path = decodeURIComponent(uriPath);
- if (path.startsWith("totp/") || path.startsWith("hotp/")) {
- path = path.substring(5);
- }
- if (path.includes(":")) {
- return path.split(":")[0];
- } else if (path.includes("-")) {
- return path.split("-")[0];
- }
- return path;
- } catch (e) {
- return "";
- }
- }
-
- private static _getDigits(uriParams): number {
- try {
- return parseInt(uriParams["digits"], 10) || Code.defaultDigits;
- } catch (e) {
- return Code.defaultDigits;
- }
- }
-
- private static _getPeriod(uriParams): number {
- try {
- return parseInt(uriParams["period"], 10) || Code.defaultPeriod;
- } catch (e) {
- return Code.defaultPeriod;
- }
- }
-
- private static _getAlgorithm(uriParams): AlgorithmType {
- try {
- const algorithm = uriParams["algorithm"].toLowerCase();
- if (algorithm === "sha256") {
- return algorithm;
- } else if (algorithm === "sha512") {
- return algorithm;
- }
- } catch (e) {
- // nothing
- }
- return "sha1";
- }
-
- private static _getType(uriPath: string): Type {
- const oauthType = uriPath.split("/")[0].substring(0);
- if (oauthType.toLowerCase() === "totp") {
- return "totp";
- } else if (oauthType.toLowerCase() === "hotp") {
- return "hotp";
- }
- throw new Error(`Unsupported format with host ${oauthType}`);
- }
-
- static getSanitizedSecret(uriParams): string {
- return uriParams["secret"].replace(/ /g, "").toUpperCase();
- }
-}