diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index 4d38e72d80..10aa86c7e0 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -20,9 +20,6 @@ "title": "airtm", "hex": "000000" }, - { - "title": "Aiven" - }, { "title": "aliyun", "altNames": [ @@ -32,22 +29,13 @@ { "title": "Amazon" }, - { - "title": "Amazon Web Services" - }, { "title": "Anycoin Direct", "slug": "anycoindirect" }, - { - "title": "Appwrite" - }, { "title": "AscendEX" }, - { - "title": "Aternos" - }, { "title": "authentik", "altNames": [ @@ -136,13 +124,11 @@ "title": "Bitwarden" }, { - "title": "Black Desert" - }, - { - "title": "Blackbaud" - }, - { - "title": "Blizzard" + "title": "Bloom Host", + "slug": "bloom_host", + "altNames": [ + "Bloom Host Billing" + ] }, { "title": "Blockchain", @@ -162,9 +148,6 @@ { "title": "Bluesky" }, - { - "title": "Bohemia" - }, { "title": "Booking", "altNames": [ @@ -178,7 +161,10 @@ ] }, { - "title": "Box" + "title": "Booking", + "altNames": [ + "Booking.com" + ] }, { "title": "Brave Creators", @@ -189,9 +175,6 @@ "Brave Browser" ] }, - { - "title": "Broadcom" - }, { "title": "Bybit" }, @@ -201,9 +184,6 @@ { "title": "Capacities" }, - { - "title": "Capcom" - }, { "title": "Carta", "altNames": [ @@ -221,9 +201,6 @@ "slug": "cih", "hex": "D14633" }, - { - "title": "Cloud66" - }, { "title": "CloudAMQP" }, @@ -233,9 +210,6 @@ { "title": "Cloudflare" }, - { - "title": "Cloudhq" - }, { "title": "Coinbase" }, @@ -282,9 +256,6 @@ "Digifinex.com" ] }, - { - "title": "Digital Ocean" - }, { "title": "DirectAdmin" }, @@ -297,16 +268,10 @@ { "title": "Doppler" }, - { - "title": "Dropbox" - }, { "title": "dus.net", "slug": "dusnet" }, - { - "title": "EA" - }, { "title": "eBay" }, @@ -321,53 +286,23 @@ ] }, { - "title": "Elastic Cloud" - }, - { - "title": "Eneba" - }, - { - "title": "Engine Yard" + "title": "ente", + "hex": "1DB954" }, { "title": "enom" }, - { - "title": "ente", - "hex": "1DB954" - }, { "title": "Epic Games", "slug": "epic_games", "hex": "000000" }, - { - "title": "Equinix Metal" - }, - { - "title": "Erai-raws" - }, { "title": "Esketit" }, - { - "title": "ESL Gaming" - }, { "title": "Estateguru" }, - { - "title": "Eve Online" - }, - { - "title": "Evernote" - }, - { - "title": "Faceit" - }, - { - "title": "Fanatical" - }, { "title": "Filen" }, @@ -375,9 +310,6 @@ "title": "Firefox", "slug": "mozilla" }, - { - "title": "Fly.io" - }, { "title": "ForUsAll" }, @@ -396,12 +328,6 @@ { "title": "Google" }, - { - "title": "Google Cloud Platform" - }, - { - "title": "Google Drive" - }, { "title": "Gosuslugi", "slug": "Gosuslugi", @@ -416,55 +342,31 @@ "Government Gateway" ] }, - { - "title": "Gree" - }, { "title": "Guideline" }, - { - "title": "Guildwars2" - }, { "title": "Gusto" }, { "title": "Habbo" }, - { - "title": "HashiCorp Cloud Platform" - }, { "title": "Healthchecks.io", "slug": "healthchecks" }, - { - "title": "Heroku" - }, - { - "title": "Hetzner" - }, { "title": "Hivelocity" }, { "title": "HTX" }, - { - "title": "Huawei Cloud" - }, { "title": "HuggingFace", "altNames": [ "Hugging Face" ] }, - { - "title": "Humble Bundle" - }, - { - "title": "IBM Cloud" - }, { "title": "IceDrive" }, @@ -472,9 +374,6 @@ "title": "ID.me", "slug": "IDme" }, - { - "title": "Idrive" - }, { "title": "Infomaniak" }, @@ -485,13 +384,13 @@ { "title": "ING" }, - { - "title": "Instagram" - }, { "title": "Instant Gaming", "slug": "instant_gaming" }, + { + "title": "Instagram" + }, { "title": "INWX" }, @@ -513,24 +412,12 @@ "坚果云" ] }, - { - "title": "Jottacloud" - }, - { - "title": "Joyent" - }, { "title": "Kagi" }, - { - "title": "KeyCDN" - }, { "title": "Kick" }, - { - "title": "Kinguin" - }, { "title": "Kite" }, @@ -568,27 +455,15 @@ "title": "La Poste", "slug": "laposte" }, - { - "title": "Laravel Forge" - }, { "title": "Lark", "altNames": [ "飞书" ] }, - { - "title": "Leaseweb" - }, { "title": "Letterboxd" }, - { - "title": "Lichess" - }, - { - "title": "Linode" - }, { "title": "Linux.Do", "slug": "LINUX_DO", @@ -610,9 +485,6 @@ "title": "Login.gov", "slug": "login_gov" }, - { - "title": "MacStadium" - }, { "title": "Marketplace.tf", "slug": "marketplacedottf" @@ -643,15 +515,9 @@ "title": "Microsoft 365", "slug": "microsoft365" }, - { - "title": "Microsoft Azure" - }, { "title": "Migros" }, - { - "title": "Minecraft" - }, { "title": "Mintos" }, @@ -662,9 +528,6 @@ "MistralAI" ] }, - { - "title": "Modrinth" - }, { "title": "Mozilla" }, @@ -696,12 +559,6 @@ "FritzBox 7583" ] }, - { - "title": "Myprimobox" - }, - { - "title": "N-able" - }, { "title": "Name.com", "slug": "name_com" @@ -714,7 +571,7 @@ ] }, { - "title": "Netlify" + "title": "NextDNS" }, { "title": "Newton", @@ -722,15 +579,6 @@ "Newton Crypto" ] }, - { - "title": "Nexon" - }, - { - "title": "NextDNS" - }, - { - "title": "Nexusmods" - }, { "title": "ngrok", "hex": "858585" @@ -744,9 +592,6 @@ { "title": "Njalla" }, - { - "title": "Nordlocker" - }, { "title": "Notesnook" }, @@ -759,25 +604,9 @@ { "title": "NVIDIA" }, - { - "title": "Obsidian" - }, { "title": "Odido" }, - { - "title": "okx", - "hex": "000000", - "altNames": [ - "欧易" - ] - }, - { - "title": "Onedrive" - }, - { - "title": "Onehub" - }, { "title": "OpenObserve", "slug": "open_observe", @@ -787,7 +616,11 @@ ] }, { - "title": "Oracle Cloud Infrastructure" + "title": "okx", + "hex": "000000", + "altNames": [ + "欧易" + ] }, { "title": "Parsec" @@ -815,19 +648,10 @@ { "title": "Pingvin Share" }, - { - "title": "PlayerAuctions" - }, - { - "title": "Playstation" - }, { "title": "Plutus", "hex": "DEC685" }, - { - "title": "Poli Systems" - }, { "title": "Poloniex" }, @@ -851,15 +675,6 @@ { "title": "Proxmox" }, - { - "title": "PSN Profiles" - }, - { - "title": "Put.io" - }, - { - "title": "Puter" - }, { "title": "qiniuyun", "altNames": [ @@ -867,12 +682,6 @@ "qiniu" ] }, - { - "title": "QNAP" - }, - { - "title": "Railway" - }, { "title": "Raindrop.io", "slug": "raindrop_io", @@ -880,12 +689,6 @@ "Raindrop" ] }, - { - "title": "Rapidgator" - }, - { - "title": "Razer" - }, { "title": "Real-Debrid", "slug": "real_debrid" @@ -915,15 +718,9 @@ "title": "Revolt", "hex": "858585" }, - { - "title": "Rewind" - }, { "title": "RippleMatch" }, - { - "title": "Roblox" - }, { "title": "Rockstar Games", "slug": "rockstar_games" @@ -939,33 +736,15 @@ { "title": "Samsung" }, - { - "title": "ScaleGrid" - }, - { - "title": "Scaleway" - }, - { - "title": "Scalr" - }, { "title": "Sendgrid" }, - { - "title": "Serverspace" - }, { "title": "service-bw" }, - { - "title": "Shadow" - }, { "title": "Shakepay" }, - { - "title": "Side Quest" - }, { "title": "SimpleLogin" }, @@ -1000,9 +779,6 @@ { "title": "Snapchat" }, - { - "title": "Square Enix" - }, { "title": "Standard Notes", "slug": "standardnotes" @@ -1010,12 +786,6 @@ { "title": "Surfshark" }, - { - "title": "Sync" - }, - { - "title": "Synology" - }, { "title": "Synology DSM", "slug": "synology_dsm" @@ -1031,7 +801,12 @@ "title": "TCPShield" }, { - "title": "Teamviewer" + "title": "tencent cloud", + "slug": "tencent_cloud", + "altNames": [ + "腾讯云", + "tencentcloud" + ] }, { "title": "Techlore", @@ -1040,9 +815,6 @@ "Techlore Forums" ] }, - { - "title": "Technic" - }, { "title": "Teleport", "altNames": [ @@ -1069,18 +841,15 @@ "title": "Termius", "hex": "858585" }, + { + "title": "Titan" + }, { "title": "tianyiyun", "altNames": [ "天翼云" ] }, - { - "title": "Tilaa" - }, - { - "title": "Titan" - }, { "title": "TorGuard" }, @@ -1139,9 +908,6 @@ "title": "Unity", "hex": "858585" }, - { - "title": "Updraftplus" - }, { "title": "Uphold" }, @@ -1158,19 +924,7 @@ ] }, { - "title": "WARGAMING.NET", - "altNames": [ - "Wargaming" - ] - }, - { - "title": "VRChat" - }, - { - "title": "Vultr" - }, - { - "title": "Warner Bros Games" + "title": "WARGAMING.NET" }, { "title": "Wealthfront" @@ -1182,9 +936,6 @@ "title": "WEB.DE", "slug": "web_de" }, - { - "title": "Wetransfer" - }, { "title": "WHMCS" }, @@ -1204,9 +955,6 @@ { "title": "WYZE" }, - { - "title": "Xbox" - }, { "title": "yahoo" }, diff --git a/auth/assets/custom-icons/icons/Aiven.svg b/auth/assets/custom-icons/icons/Aiven.svg deleted file mode 100644 index a04e49afe1..0000000000 --- a/auth/assets/custom-icons/icons/Aiven.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Amazon Web Services.svg b/auth/assets/custom-icons/icons/Amazon Web Services.svg deleted file mode 100644 index 03c912db39..0000000000 --- a/auth/assets/custom-icons/icons/Amazon Web Services.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Appwrite.svg b/auth/assets/custom-icons/icons/Appwrite.svg deleted file mode 100644 index 98bffa5011..0000000000 --- a/auth/assets/custom-icons/icons/Appwrite.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Aternos.svg b/auth/assets/custom-icons/icons/Aternos.svg deleted file mode 100644 index 9e0decb1cd..0000000000 --- a/auth/assets/custom-icons/icons/Aternos.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/auth/assets/custom-icons/icons/Black Desert.svg b/auth/assets/custom-icons/icons/Black Desert.svg deleted file mode 100644 index a0659e8278..0000000000 --- a/auth/assets/custom-icons/icons/Black Desert.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Blackbaud.svg b/auth/assets/custom-icons/icons/Blackbaud.svg deleted file mode 100644 index 67d3dd8557..0000000000 --- a/auth/assets/custom-icons/icons/Blackbaud.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Blizzard.svg b/auth/assets/custom-icons/icons/Blizzard.svg deleted file mode 100644 index cc454d6776..0000000000 --- a/auth/assets/custom-icons/icons/Blizzard.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Bohemia.svg b/auth/assets/custom-icons/icons/Bohemia.svg deleted file mode 100644 index f9047dfdf3..0000000000 --- a/auth/assets/custom-icons/icons/Bohemia.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Box.svg b/auth/assets/custom-icons/icons/Box.svg deleted file mode 100644 index acfaaa1866..0000000000 --- a/auth/assets/custom-icons/icons/Box.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Broadcom.svg b/auth/assets/custom-icons/icons/Broadcom.svg deleted file mode 100644 index b957092c0b..0000000000 --- a/auth/assets/custom-icons/icons/Broadcom.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Capcom.svg b/auth/assets/custom-icons/icons/Capcom.svg deleted file mode 100644 index df1117281d..0000000000 --- a/auth/assets/custom-icons/icons/Capcom.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Cloud66.svg b/auth/assets/custom-icons/icons/Cloud66.svg deleted file mode 100644 index b006121811..0000000000 --- a/auth/assets/custom-icons/icons/Cloud66.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Cloudhq.svg b/auth/assets/custom-icons/icons/Cloudhq.svg deleted file mode 100644 index 0a4ea4d8e6..0000000000 --- a/auth/assets/custom-icons/icons/Cloudhq.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Digital Ocean.svg b/auth/assets/custom-icons/icons/Digital Ocean.svg deleted file mode 100644 index 4f4fc25ac7..0000000000 --- a/auth/assets/custom-icons/icons/Digital Ocean.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Dropbox.svg b/auth/assets/custom-icons/icons/Dropbox.svg deleted file mode 100644 index 80fb667116..0000000000 --- a/auth/assets/custom-icons/icons/Dropbox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/EA.svg b/auth/assets/custom-icons/icons/EA.svg deleted file mode 100644 index 9eb88938d9..0000000000 --- a/auth/assets/custom-icons/icons/EA.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/auth/assets/custom-icons/icons/ESL Gaming.svg b/auth/assets/custom-icons/icons/ESL Gaming.svg deleted file mode 100644 index e5fbc5f9f5..0000000000 --- a/auth/assets/custom-icons/icons/ESL Gaming.svg +++ /dev/null @@ -1 +0,0 @@ -ESLGaming icon diff --git a/auth/assets/custom-icons/icons/Elastic Cloud.svg b/auth/assets/custom-icons/icons/Elastic Cloud.svg deleted file mode 100644 index 8994145a3b..0000000000 --- a/auth/assets/custom-icons/icons/Elastic Cloud.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Eneba.svg b/auth/assets/custom-icons/icons/Eneba.svg deleted file mode 100644 index 109a2558cf..0000000000 --- a/auth/assets/custom-icons/icons/Eneba.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Engine Yard.svg b/auth/assets/custom-icons/icons/Engine Yard.svg deleted file mode 100644 index ba3c3ff4f8..0000000000 --- a/auth/assets/custom-icons/icons/Engine Yard.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Equinix Metal.svg b/auth/assets/custom-icons/icons/Equinix Metal.svg deleted file mode 100644 index 2674b8efae..0000000000 --- a/auth/assets/custom-icons/icons/Equinix Metal.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Erai-raws.svg b/auth/assets/custom-icons/icons/Erai-raws.svg deleted file mode 100644 index 44b1536b68..0000000000 --- a/auth/assets/custom-icons/icons/Erai-raws.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Eve Online.svg b/auth/assets/custom-icons/icons/Eve Online.svg deleted file mode 100644 index ec5306dafd..0000000000 --- a/auth/assets/custom-icons/icons/Eve Online.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Evernote.svg b/auth/assets/custom-icons/icons/Evernote.svg deleted file mode 100644 index f7bc7f4ed4..0000000000 --- a/auth/assets/custom-icons/icons/Evernote.svg +++ /dev/null @@ -1 +0,0 @@ -Evernote-colorCreated with Sketch. diff --git a/auth/assets/custom-icons/icons/Faceit.svg b/auth/assets/custom-icons/icons/Faceit.svg deleted file mode 100644 index 316ef2c91a..0000000000 --- a/auth/assets/custom-icons/icons/Faceit.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/auth/assets/custom-icons/icons/Fanatical.svg b/auth/assets/custom-icons/icons/Fanatical.svg deleted file mode 100644 index cfcff35bb8..0000000000 --- a/auth/assets/custom-icons/icons/Fanatical.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Fly.io.svg b/auth/assets/custom-icons/icons/Fly.io.svg deleted file mode 100644 index 4fb92ab2de..0000000000 --- a/auth/assets/custom-icons/icons/Fly.io.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Google Cloud Platform.svg b/auth/assets/custom-icons/icons/Google Cloud Platform.svg deleted file mode 100644 index 2516a83b37..0000000000 --- a/auth/assets/custom-icons/icons/Google Cloud Platform.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Google Drive.svg b/auth/assets/custom-icons/icons/Google Drive.svg deleted file mode 100644 index 2748e3ef6f..0000000000 --- a/auth/assets/custom-icons/icons/Google Drive.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/auth/assets/custom-icons/icons/Gree.svg b/auth/assets/custom-icons/icons/Gree.svg deleted file mode 100644 index 106bfe16ca..0000000000 --- a/auth/assets/custom-icons/icons/Gree.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Guildwars2.svg b/auth/assets/custom-icons/icons/Guildwars2.svg deleted file mode 100644 index 810f10d8b8..0000000000 --- a/auth/assets/custom-icons/icons/Guildwars2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/HashiCorp Cloud Platform.svg b/auth/assets/custom-icons/icons/HashiCorp Cloud Platform.svg deleted file mode 100644 index 2de83aa625..0000000000 --- a/auth/assets/custom-icons/icons/HashiCorp Cloud Platform.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Heroku.svg b/auth/assets/custom-icons/icons/Heroku.svg deleted file mode 100644 index 485e4879a7..0000000000 --- a/auth/assets/custom-icons/icons/Heroku.svg +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Hetzner.svg b/auth/assets/custom-icons/icons/Hetzner.svg deleted file mode 100644 index 3729640e36..0000000000 --- a/auth/assets/custom-icons/icons/Hetzner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Huawei Cloud.svg b/auth/assets/custom-icons/icons/Huawei Cloud.svg deleted file mode 100644 index b3007b4b71..0000000000 --- a/auth/assets/custom-icons/icons/Huawei Cloud.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Humble Bundle.svg b/auth/assets/custom-icons/icons/Humble Bundle.svg deleted file mode 100644 index 110dc2d2da..0000000000 --- a/auth/assets/custom-icons/icons/Humble Bundle.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/auth/assets/custom-icons/icons/IBM Cloud.svg b/auth/assets/custom-icons/icons/IBM Cloud.svg deleted file mode 100644 index 15d748d49e..0000000000 --- a/auth/assets/custom-icons/icons/IBM Cloud.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Idrive.svg b/auth/assets/custom-icons/icons/Idrive.svg deleted file mode 100644 index ed72acffe0..0000000000 --- a/auth/assets/custom-icons/icons/Idrive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Jottacloud.svg b/auth/assets/custom-icons/icons/Jottacloud.svg deleted file mode 100644 index cacad44c46..0000000000 --- a/auth/assets/custom-icons/icons/Jottacloud.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Joyent.svg b/auth/assets/custom-icons/icons/Joyent.svg deleted file mode 100644 index c27260a6a3..0000000000 --- a/auth/assets/custom-icons/icons/Joyent.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/KeyCDN.svg b/auth/assets/custom-icons/icons/KeyCDN.svg deleted file mode 100644 index e15d348834..0000000000 --- a/auth/assets/custom-icons/icons/KeyCDN.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Kinguin.svg b/auth/assets/custom-icons/icons/Kinguin.svg deleted file mode 100644 index 94ec1f5178..0000000000 --- a/auth/assets/custom-icons/icons/Kinguin.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Laravel Forge.svg b/auth/assets/custom-icons/icons/Laravel Forge.svg deleted file mode 100644 index 00a2e3b2ab..0000000000 --- a/auth/assets/custom-icons/icons/Laravel Forge.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Leaseweb.svg b/auth/assets/custom-icons/icons/Leaseweb.svg deleted file mode 100644 index 302a040787..0000000000 --- a/auth/assets/custom-icons/icons/Leaseweb.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Lichess.svg b/auth/assets/custom-icons/icons/Lichess.svg deleted file mode 100644 index 1597ea06a3..0000000000 --- a/auth/assets/custom-icons/icons/Lichess.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Linode.svg b/auth/assets/custom-icons/icons/Linode.svg deleted file mode 100644 index bacf830273..0000000000 --- a/auth/assets/custom-icons/icons/Linode.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/MacStadium.svg b/auth/assets/custom-icons/icons/MacStadium.svg deleted file mode 100644 index 02cf61ad00..0000000000 --- a/auth/assets/custom-icons/icons/MacStadium.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Microsoft Azure.svg b/auth/assets/custom-icons/icons/Microsoft Azure.svg deleted file mode 100644 index 43928680b9..0000000000 --- a/auth/assets/custom-icons/icons/Microsoft Azure.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - Miscellaneous - - - microsoft-azure - - - Microsoft Azure - - - image/svg+xml - - - Amido Limited - - - Richard Slater - - - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Minecraft.svg b/auth/assets/custom-icons/icons/Minecraft.svg deleted file mode 100644 index 8435ae985d..0000000000 --- a/auth/assets/custom-icons/icons/Minecraft.svg +++ /dev/null @@ -1 +0,0 @@ -image/svg+xml diff --git a/auth/assets/custom-icons/icons/Modrinth.svg b/auth/assets/custom-icons/icons/Modrinth.svg deleted file mode 100644 index fabdf608ad..0000000000 --- a/auth/assets/custom-icons/icons/Modrinth.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Myprimobox.svg b/auth/assets/custom-icons/icons/Myprimobox.svg deleted file mode 100644 index 2b0df1e198..0000000000 --- a/auth/assets/custom-icons/icons/Myprimobox.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/N-able.svg b/auth/assets/custom-icons/icons/N-able.svg deleted file mode 100644 index 162b8b5698..0000000000 --- a/auth/assets/custom-icons/icons/N-able.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Netlify.svg b/auth/assets/custom-icons/icons/Netlify.svg deleted file mode 100644 index a619161231..0000000000 --- a/auth/assets/custom-icons/icons/Netlify.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Nexon.svg b/auth/assets/custom-icons/icons/Nexon.svg deleted file mode 100644 index 623ea66c27..0000000000 --- a/auth/assets/custom-icons/icons/Nexon.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Nexusmods.svg b/auth/assets/custom-icons/icons/Nexusmods.svg deleted file mode 100644 index cbf5a43432..0000000000 --- a/auth/assets/custom-icons/icons/Nexusmods.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Nordlocker.svg b/auth/assets/custom-icons/icons/Nordlocker.svg deleted file mode 100644 index 8878e3fcda..0000000000 --- a/auth/assets/custom-icons/icons/Nordlocker.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Obsidian.svg b/auth/assets/custom-icons/icons/Obsidian.svg deleted file mode 100644 index 273588fb73..0000000000 --- a/auth/assets/custom-icons/icons/Obsidian.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Onedrive.svg b/auth/assets/custom-icons/icons/Onedrive.svg deleted file mode 100644 index 3c820c4f94..0000000000 --- a/auth/assets/custom-icons/icons/Onedrive.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Onehub.svg b/auth/assets/custom-icons/icons/Onehub.svg deleted file mode 100644 index 922cd3d9a0..0000000000 --- a/auth/assets/custom-icons/icons/Onehub.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Oracle Cloud Infrastructure.svg b/auth/assets/custom-icons/icons/Oracle Cloud Infrastructure.svg deleted file mode 100644 index 7b0e1a3ea5..0000000000 --- a/auth/assets/custom-icons/icons/Oracle Cloud Infrastructure.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/PSN Profiles.svg b/auth/assets/custom-icons/icons/PSN Profiles.svg deleted file mode 100644 index 1d2453ee72..0000000000 --- a/auth/assets/custom-icons/icons/PSN Profiles.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/PlayerAuctions.svg b/auth/assets/custom-icons/icons/PlayerAuctions.svg deleted file mode 100644 index 396a44e8da..0000000000 --- a/auth/assets/custom-icons/icons/PlayerAuctions.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Playstation.svg b/auth/assets/custom-icons/icons/Playstation.svg deleted file mode 100644 index 34dda9068d..0000000000 --- a/auth/assets/custom-icons/icons/Playstation.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Poli Systems.svg b/auth/assets/custom-icons/icons/Poli Systems.svg deleted file mode 100644 index e6104e33d5..0000000000 --- a/auth/assets/custom-icons/icons/Poli Systems.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Put.io.svg b/auth/assets/custom-icons/icons/Put.io.svg deleted file mode 100644 index 116608fd18..0000000000 --- a/auth/assets/custom-icons/icons/Put.io.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Puter.svg b/auth/assets/custom-icons/icons/Puter.svg deleted file mode 100644 index 79b76d19d1..0000000000 --- a/auth/assets/custom-icons/icons/Puter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/QNAP.svg b/auth/assets/custom-icons/icons/QNAP.svg deleted file mode 100644 index daf51717e2..0000000000 --- a/auth/assets/custom-icons/icons/QNAP.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Railway.svg b/auth/assets/custom-icons/icons/Railway.svg deleted file mode 100644 index 619ce5071f..0000000000 --- a/auth/assets/custom-icons/icons/Railway.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Rapidgator.svg b/auth/assets/custom-icons/icons/Rapidgator.svg deleted file mode 100644 index 03cd6183ff..0000000000 --- a/auth/assets/custom-icons/icons/Rapidgator.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Razer.svg b/auth/assets/custom-icons/icons/Razer.svg deleted file mode 100644 index 3b8d6fc440..0000000000 --- a/auth/assets/custom-icons/icons/Razer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Restorecord.svg b/auth/assets/custom-icons/icons/Restorecord.svg deleted file mode 100644 index 1d1894e95b..0000000000 --- a/auth/assets/custom-icons/icons/Restorecord.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Rewind.svg b/auth/assets/custom-icons/icons/Rewind.svg deleted file mode 100644 index ede9b155fc..0000000000 --- a/auth/assets/custom-icons/icons/Rewind.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Roblox.svg b/auth/assets/custom-icons/icons/Roblox.svg deleted file mode 100644 index 1a7a75c16e..0000000000 --- a/auth/assets/custom-icons/icons/Roblox.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/ScaleGrid.svg b/auth/assets/custom-icons/icons/ScaleGrid.svg deleted file mode 100644 index b8a4bf1a26..0000000000 --- a/auth/assets/custom-icons/icons/ScaleGrid.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Scaleway.svg b/auth/assets/custom-icons/icons/Scaleway.svg deleted file mode 100644 index de67221b54..0000000000 --- a/auth/assets/custom-icons/icons/Scaleway.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Scalr.svg b/auth/assets/custom-icons/icons/Scalr.svg deleted file mode 100644 index c35d1d61e4..0000000000 --- a/auth/assets/custom-icons/icons/Scalr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Serverspace.svg b/auth/assets/custom-icons/icons/Serverspace.svg deleted file mode 100644 index d8bafd2bf1..0000000000 --- a/auth/assets/custom-icons/icons/Serverspace.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Shadow.svg b/auth/assets/custom-icons/icons/Shadow.svg deleted file mode 100644 index a116c4149f..0000000000 --- a/auth/assets/custom-icons/icons/Shadow.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Side Quest.svg b/auth/assets/custom-icons/icons/Side Quest.svg deleted file mode 100644 index aaf0eaa2a0..0000000000 --- a/auth/assets/custom-icons/icons/Side Quest.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Square Enix.svg b/auth/assets/custom-icons/icons/Square Enix.svg deleted file mode 100644 index 087fb52d58..0000000000 --- a/auth/assets/custom-icons/icons/Square Enix.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Sync.svg b/auth/assets/custom-icons/icons/Sync.svg deleted file mode 100644 index 155232a651..0000000000 --- a/auth/assets/custom-icons/icons/Sync.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Synology.svg b/auth/assets/custom-icons/icons/Synology.svg deleted file mode 100644 index a37b86aceb..0000000000 --- a/auth/assets/custom-icons/icons/Synology.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/TeamViewer.svg b/auth/assets/custom-icons/icons/TeamViewer.svg deleted file mode 100644 index 7afa42596a..0000000000 --- a/auth/assets/custom-icons/icons/TeamViewer.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Technic.svg b/auth/assets/custom-icons/icons/Technic.svg deleted file mode 100644 index 53b1cafb17..0000000000 --- a/auth/assets/custom-icons/icons/Technic.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Tilaa.svg b/auth/assets/custom-icons/icons/Tilaa.svg deleted file mode 100644 index c20371be4c..0000000000 --- a/auth/assets/custom-icons/icons/Tilaa.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - diff --git a/auth/assets/custom-icons/icons/Updraftplus.svg b/auth/assets/custom-icons/icons/Updraftplus.svg deleted file mode 100644 index afc9bb32b8..0000000000 --- a/auth/assets/custom-icons/icons/Updraftplus.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/VRChat.svg b/auth/assets/custom-icons/icons/VRChat.svg deleted file mode 100644 index d317e9a0b2..0000000000 --- a/auth/assets/custom-icons/icons/VRChat.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/auth/assets/custom-icons/icons/Vultr.svg b/auth/assets/custom-icons/icons/Vultr.svg deleted file mode 100644 index c6e47b0856..0000000000 --- a/auth/assets/custom-icons/icons/Vultr.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Warner Bros Games.svg b/auth/assets/custom-icons/icons/Warner Bros Games.svg deleted file mode 100644 index b10a089caf..0000000000 --- a/auth/assets/custom-icons/icons/Warner Bros Games.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Wetransfer.svg b/auth/assets/custom-icons/icons/Wetransfer.svg deleted file mode 100644 index dfa63e15a7..0000000000 --- a/auth/assets/custom-icons/icons/Wetransfer.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/auth/assets/custom-icons/icons/Xbox.svg b/auth/assets/custom-icons/icons/Xbox.svg deleted file mode 100644 index 249c685d74..0000000000 --- a/auth/assets/custom-icons/icons/Xbox.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/auth/assets/simple-icons b/auth/assets/simple-icons index 8a3731352a..954790ce65 160000 --- a/auth/assets/simple-icons +++ b/auth/assets/simple-icons @@ -1 +1 @@ -Subproject commit 8a3731352af133a02223a6c7b1f37c4abb096af0 +Subproject commit 954790ce652942533e9e59bfb9c8cc7e99962f88 diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index f94b46875a..8a8093bce5 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 4.1.0+410 +version: 4.1.1+411 publish_to: none environment: diff --git a/desktop/package.json b/desktop/package.json index 933879d4f3..68f6d275b2 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -15,7 +15,7 @@ "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-main": "tsc && electron .", - "dev-renderer": "cross-env-shell _ENTE_IS_DESKTOP=1 \"cd ../web && yarn install && yarn dev:photos\"", + "dev-renderer": "cross-env-shell _ENTE_IS_DESKTOP=1 \"cd ../web && yarn install && yarn workspace photos next dev -p 3008\"", "postinstall": "electron-builder install-app-deps", "lint": "yarn prettier --check --log-level warn . && yarn eslint && yarn tsc", "lint-fix": "yarn prettier --write --log-level warn . && yarn eslint && yarn tsc" diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 4ebe565bca..d187b1f795 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -187,13 +187,13 @@ const logStartupBanner = () => { * * It uses protocol handlers to serve files from the "ente://" protocol. * - * - In development this is proxied to http://localhost:3000 + * - In development this is proxied to http://localhost:3008 * - In production it serves files from the `/out` directory * * For more details, see this comparison: * https://github.com/HaNdTriX/next-electron-server/issues/5 */ -const setupRendererServer = () => serveNextAt(rendererURL); +const setupRendererServer = () => serveNextAt(rendererURL, { port: 3008 }); /** * Register privileged schemes. diff --git a/desktop/src/main/services/ffmpeg.ts b/desktop/src/main/services/ffmpeg.ts index 6ba4bf52f0..2ffd37a19b 100644 --- a/desktop/src/main/services/ffmpeg.ts +++ b/desktop/src/main/services/ffmpeg.ts @@ -9,7 +9,7 @@ import { makeTempFilePath, } from "../utils/temp"; -/* Duplicated in the web app's code (used by the WASM FFmpeg implementation). */ +/* Ditto in the web app's code (used by the WASM FFmpeg invocation). */ const ffmpegPathPlaceholder = "FFMPEG"; const inputPathPlaceholder = "INPUT"; const outputPathPlaceholder = "OUTPUT"; diff --git a/docs/docs/auth/faq/index.md b/docs/docs/auth/faq/index.md index 057e055507..5e631906aa 100644 --- a/docs/docs/auth/faq/index.md +++ b/docs/docs/auth/faq/index.md @@ -25,6 +25,11 @@ at [ente.io](https://ente.io). You can enable FaceID lock under Settings → Security → Lockscreen. +### How secure is the lock screen provided by Ente Auth? + +Auth's lock screen acts as a barrier to prevent an attacker from accessing the +contents of the app. It does not introduce a layer of cryptographic security. + ### Why do the desktop and mobile apps display different codes? Please verify that the time on both your mobile and desktop is the same. diff --git a/mobile/docs/translations.md b/mobile/docs/translations.md new file mode 100644 index 0000000000..104bd482ce --- /dev/null +++ b/mobile/docs/translations.md @@ -0,0 +1,49 @@ +# Translations + +We use Crowdin for translations, and the `intl` package to load these at +runtime. + +Within our project we have the _source_ strings - these are the key value pairs +in the `lib/l10n/intl_en.arb` file. + +Volunteers can add a new _translation_ in their language corresponding to each +such source key-value to our +[Crowdin project](https://crowdin.com/project/ente-photos-app). + +When a new source string is added, we run a [GitHub workflow](../../.github/workflows/mobile-crowdin-push.yml) +that + +- Uploads sources to Crowdin - So any new key value pair we add in the source + `intl_en.arb` becomes available to translators to translate. + +Every monday, we run a [GitHub workflow](../../.github/workflows/mobile-crowdin-sync.yml) +that + +- Downloads translations from Crowdin - So any new translations that + translators have made on the Crowdin dashboard (for existing sources) will + be added to the corresponding `intl_XX.arb`. + +The workflow also uploads existing translations and also downloads new sources +from Crowdin, but these two should be no-ops. + +## Adding a new string + +- Add a new entry in `lib/l10n/intl_en.arb` (the + **source `intl_en.arb`**). +- Use the new key in code with the `S` class + (`import "package:photos/generated/l10n.dart"`). +- During the next sync, the workflow will upload this source item to Crowdin's + dashboard, allowing translators to translate it. + +## Updating an existing string + +- Update the existing value for the key in the source `intl_en.arb`. +- During the next sync, the workflow will clear out all the existing + translations so that they can be translated afresh. + +## Deleting an existing string + +- Remove the key value pair from the source `intl_en.arb`. +- During the next sync, the workflow will delete that source item from all + existing translations (both in the Crowdin project and also from the + other `intl_XX.arb` files in the repository). diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 05b45bbd44..f460ab13dc 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -518,4 +518,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/mobile/lib/db/ml/db.dart b/mobile/lib/db/ml/db.dart index 6ed5b85e3c..b3e3fa232a 100644 --- a/mobile/lib/db/ml/db.dart +++ b/mobile/lib/db/ml/db.dart @@ -17,14 +17,17 @@ import "package:photos/services/machine_learning/ml_result.dart"; import "package:photos/utils/ml_util.dart"; import 'package:sqlite_async/sqlite_async.dart'; -/// Stores all data for the FacesML-related features. The database can be accessed by `MLDataDB.instance.database`. +/// Stores all data for the ML related features. The database can be accessed by `MLDataDB.instance.database`. /// /// This includes: /// [facesTable] - Stores all the detected faces and its embeddings in the images. -/// [createFaceClustersTable] - Stores all the mappings from the faces (faceID) to the clusters (clusterID). +/// [faceClustersTable] - Stores all the mappings from the faces (faceID) to the clusters (clusterID). /// [clusterPersonTable] - Stores all the clusters that are mapped to a certain person. /// [clusterSummaryTable] - Stores a summary of each cluster, containg the mean embedding and the number of faces in the cluster. /// [notPersonFeedback] - Stores the clusters that are confirmed not to belong to a certain person by the user +/// +/// [clipTable] - Stores the embeddings of the CLIP model +/// [fileDataTable] - Stores data about the files that are already processed by the ML models class MLDataDB { static final Logger _logger = Logger("MLDataDB"); @@ -477,6 +480,25 @@ class MLDataDB { return result; } + Future>> getClusterIdToFaceIdsForPerson( + String personID, + ) async { + final db = await instance.asyncDB; + final List> maps = await db.getAll( + 'SELECT $faceClustersTable.$clusterIDColumn, $faceIDColumn FROM $clusterPersonTable ' + 'INNER JOIN $faceClustersTable ON $clusterPersonTable.$clusterIDColumn = $faceClustersTable.$clusterIDColumn ' + 'WHERE $personIdColumn = ?', + [personID], + ); + final Map> result = {}; + for (final map in maps) { + final clusterID = map[clusterIDColumn] as String; + final faceID = map[faceIDColumn] as String; + result.putIfAbsent(clusterID, () => {}).add(faceID); + } + return result; + } + Future> getFaceIDsForPerson(String personID) async { final db = await instance.asyncDB; final faceIdsResult = await db.getAll( @@ -553,6 +575,19 @@ class MLDataDB { await db.executeBatch(sql, parameterSets); } + Future removeFaceIdToClusterId( + Map faceIDToClusterID, + ) async { + final db = await instance.asyncDB; + const String sql = ''' + DELETE FROM $faceClustersTable + WHERE $faceIDColumn = ? AND $clusterIDColumn = ? + '''; + final parameterSets = + faceIDToClusterID.entries.map((e) => [e.key, e.value]).toList(); + await db.executeBatch(sql, parameterSets); + } + Future removePerson(String personID) async { final db = await instance.asyncDB; diff --git a/mobile/lib/generated/intl/messages_ar.dart b/mobile/lib/generated/intl/messages_ar.dart index 406efc0cce..1ce7ba622c 100644 --- a/mobile/lib/generated/intl/messages_ar.dart +++ b/mobile/lib/generated/intl/messages_ar.dart @@ -27,12 +27,8 @@ class MessageLookup extends MessageLookupByLibrary { "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( "أُدركُ أنّني فقدتُ كلمة مروري، فقد أفقد بياناتي لأن بياناتي مشفرة تشفيرًا تامًّا من النهاية إلى النهاية."), "cancel": MessageLookupByLibrary.simpleMessage("إلغاء"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "decrypting": MessageLookupByLibrary.simpleMessage("فك التشفير..."), "email": MessageLookupByLibrary.simpleMessage("البريد الإلكتروني"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage("أدخل عنوان بريدك الإلكتروني"), "enterYourRecoveryKey": @@ -52,12 +48,6 @@ class MessageLookup extends MessageLookupByLibrary { "recoverButton": MessageLookupByLibrary.simpleMessage("استرداد"), "recoverySuccessful": MessageLookupByLibrary.simpleMessage("نجح الاسترداد!"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "sorry": MessageLookupByLibrary.simpleMessage("المعذرة"), "terminate": MessageLookupByLibrary.simpleMessage("إنهاء"), "terminateSession": diff --git a/mobile/lib/generated/intl/messages_be.dart b/mobile/lib/generated/intl/messages_be.dart index 9092285095..a4a49d3b64 100644 --- a/mobile/lib/generated/intl/messages_be.dart +++ b/mobile/lib/generated/intl/messages_be.dart @@ -56,8 +56,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Змяніць пароль"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Праверце свае ўваходныя лісты (і спам) для завяршэння праверкі"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "codeAppliedPageTitle": MessageLookupByLibrary.simpleMessage("Код ужыты"), "confirm": MessageLookupByLibrary.simpleMessage("Пацвердзіць"), @@ -112,8 +110,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Зрабіць гэта пазней"), "done": MessageLookupByLibrary.simpleMessage("Гатова"), "email": MessageLookupByLibrary.simpleMessage("Электронная пошта"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("Шыфраванне"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("Ключы шыфравання"), @@ -223,12 +219,6 @@ class MessageLookup extends MessageLookupByLibrary { "retry": MessageLookupByLibrary.simpleMessage("Паўтарыць"), "saveKey": MessageLookupByLibrary.simpleMessage("Захаваць ключ"), "scanCode": MessageLookupByLibrary.simpleMessage("Сканіраваць код"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "security": MessageLookupByLibrary.simpleMessage("Бяспека"), "selectAll": MessageLookupByLibrary.simpleMessage("Абраць усё"), "selectReason": diff --git a/mobile/lib/generated/intl/messages_bg.dart b/mobile/lib/generated/intl/messages_bg.dart index a23489ec1f..e887127f40 100644 --- a/mobile/lib/generated/intl/messages_bg.dart +++ b/mobile/lib/generated/intl/messages_bg.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'bg'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_ca.dart b/mobile/lib/generated/intl/messages_ca.dart index 2b8f5a7ebc..84dea987b0 100644 --- a/mobile/lib/generated/intl/messages_ca.dart +++ b/mobile/lib/generated/intl/messages_ca.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ca'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_cs.dart b/mobile/lib/generated/intl/messages_cs.dart index a2a98616d5..226e365e9c 100644 --- a/mobile/lib/generated/intl/messages_cs.dart +++ b/mobile/lib/generated/intl/messages_cs.dart @@ -26,16 +26,6 @@ class MessageLookup extends MessageLookupByLibrary { "Jaký je váš hlavní důvod, proč mažete svůj účet?"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Zkontrolujte prosím svou doručenou poštu (a spam) pro dokončení ověření"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage(""), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") + "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage("") }; } diff --git a/mobile/lib/generated/intl/messages_da.dart b/mobile/lib/generated/intl/messages_da.dart index 5c2005ea4d..c47bf7b6c6 100644 --- a/mobile/lib/generated/intl/messages_da.dart +++ b/mobile/lib/generated/intl/messages_da.dart @@ -41,8 +41,6 @@ class MessageLookup extends MessageLookupByLibrary { "backedUpFolders": MessageLookupByLibrary.simpleMessage("Sikkerhedskopierede mapper"), "cancel": MessageLookupByLibrary.simpleMessage("Annuller"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("Bekræft Sletning Af Konto"), "confirmPassword": @@ -73,8 +71,6 @@ class MessageLookup extends MessageLookupByLibrary { "developerSettingsWarning": MessageLookupByLibrary.simpleMessage( "Er du sikker på, at du vil ændre udviklerindstillingerne?"), "email": MessageLookupByLibrary.simpleMessage("Email"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enterPin": MessageLookupByLibrary.simpleMessage("Indtast PIN"), "enterValidEmail": MessageLookupByLibrary.simpleMessage( "Indtast venligst en gyldig email adresse."), @@ -118,14 +114,8 @@ class MessageLookup extends MessageLookupByLibrary { "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Skan denne QR-kode med godkendelses-appen"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchHint1": MessageLookupByLibrary.simpleMessage("Hurtig, søgning på enheden"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "selectReason": MessageLookupByLibrary.simpleMessage("Vælg årsag"), "selectedPhotos": m4, "sendEmail": MessageLookupByLibrary.simpleMessage("Send email"), diff --git a/mobile/lib/generated/intl/messages_de.dart b/mobile/lib/generated/intl/messages_de.dart index 1a22db95fe..181384fa21 100644 --- a/mobile/lib/generated/intl/messages_de.dart +++ b/mobile/lib/generated/intl/messages_de.dart @@ -473,7 +473,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Status überprüfen"), "checking": MessageLookupByLibrary.simpleMessage("Wird geprüft..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Prüfe Modelle..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Freien Speicher einlösen"), "claimMore": MessageLookupByLibrary.simpleMessage("Mehr einlösen!"), @@ -739,7 +739,7 @@ class MessageLookup extends MessageLookupByLibrary { "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente unterstützt maschinelles Lernen für Gesichtserkennung, magische Suche und andere erweiterte Suchfunktionen auf dem Gerät"), "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), + "Aktiviere maschinelles Lernen für die magische Suche und Gesichtserkennung"), "enableMaps": MessageLookupByLibrary.simpleMessage("Karten aktivieren"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Dies zeigt Ihre Fotos auf einer Weltkarte.\n\nDiese Karte wird von OpenStreetMap gehostet und die genauen Standorte Ihrer Fotos werden niemals geteilt.\n\nSie können diese Funktion jederzeit in den Einstellungen deaktivieren."), @@ -1437,7 +1437,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Suche nach Datum, Monat oder Jahr"), "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), + "Bilder werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Personen werden hier angezeigt, sobald die Indizierung abgeschlossen ist"), "searchFileTypesAndNamesEmptySection": @@ -1456,7 +1456,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Laden Sie Personen ein, damit Sie geteilte Fotos hier einsehen können"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), + "Personen werden hier angezeigt, sobald die Verarbeitung abgeschlossen ist"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Sicherheit"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_el.dart b/mobile/lib/generated/intl/messages_el.dart index e0b453fe8d..79c0433b27 100644 --- a/mobile/lib/generated/intl/messages_el.dart +++ b/mobile/lib/generated/intl/messages_el.dart @@ -22,17 +22,7 @@ class MessageLookup extends MessageLookupByLibrary { final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage( - "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") + "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας") }; } diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 50c3acbaf2..193726cc47 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -59,6 +59,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m18(albumName) => "Collaborative link created for ${albumName}"; + static String m74(count) => + "${Intl.plural(count, zero: 'Added 0 collaborator', one: 'Added 1 collaborator', other: 'Added ${count} collaborators')}"; + static String m19(familyAdminEmail) => "Please contact ${familyAdminEmail} to manage your subscription"; @@ -214,6 +217,9 @@ class MessageLookup extends MessageLookupByLibrary { static String m71(email) => "Verify ${email}"; + static String m75(count) => + "${Intl.plural(count, zero: 'Added 0 viewer', one: 'Added 1 viewer', other: 'Added ${count} viewers')}"; + static String m2(email) => "We have sent a mail to ${email}"; static String m72(count) => @@ -408,6 +414,7 @@ class MessageLookup extends MessageLookupByLibrary { "backupStatusDescription": MessageLookupByLibrary.simpleMessage( "Items that have been backed up will show up here"), "backupVideos": MessageLookupByLibrary.simpleMessage("Backup videos"), + "birthday": MessageLookupByLibrary.simpleMessage("Birthday"), "blackFridaySale": MessageLookupByLibrary.simpleMessage("Black Friday Sale"), "blog": MessageLookupByLibrary.simpleMessage("Blog"), @@ -493,6 +500,7 @@ class MessageLookup extends MessageLookupByLibrary { "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Collaborators can add photos and videos to the shared album."), + "collaboratorsSuccessfullyAdded": m74, "collageLayout": MessageLookupByLibrary.simpleMessage("Layout"), "collageSaved": MessageLookupByLibrary.simpleMessage("Collage saved to gallery"), @@ -695,6 +703,7 @@ class MessageLookup extends MessageLookupByLibrary { "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"), "editLocationTagTitle": MessageLookupByLibrary.simpleMessage("Edit location"), + "editPerson": MessageLookupByLibrary.simpleMessage("Edit person"), "editsSaved": MessageLookupByLibrary.simpleMessage("Edits saved"), "editsToLocationWillOnlyBeSeenWithinEnte": MessageLookupByLibrary.simpleMessage( @@ -741,9 +750,12 @@ class MessageLookup extends MessageLookupByLibrary { "enterCode": MessageLookupByLibrary.simpleMessage("Enter code"), "enterCodeDescription": MessageLookupByLibrary.simpleMessage( "Enter the code provided by your friend to claim free storage for both of you"), + "enterDateOfBirth": + MessageLookupByLibrary.simpleMessage("Birthday (optional)"), "enterEmail": MessageLookupByLibrary.simpleMessage("Enter email"), "enterFileName": MessageLookupByLibrary.simpleMessage("Enter file name"), + "enterName": MessageLookupByLibrary.simpleMessage("Enter name"), "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "Enter a new password we can use to encrypt your data"), "enterPassword": MessageLookupByLibrary.simpleMessage("Enter password"), @@ -821,6 +833,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Files saved to gallery"), "findPeopleByName": MessageLookupByLibrary.simpleMessage("Find people quickly by name"), + "findThemQuickly": + MessageLookupByLibrary.simpleMessage("Find them quickly"), "flip": MessageLookupByLibrary.simpleMessage("Flip"), "forYourMemories": MessageLookupByLibrary.simpleMessage("for your memories"), @@ -1035,6 +1049,7 @@ class MessageLookup extends MessageLookupByLibrary { "merchandise": MessageLookupByLibrary.simpleMessage("Merchandise"), "mergeWithExisting": MessageLookupByLibrary.simpleMessage("Merge with existing"), + "mergedPhotos": MessageLookupByLibrary.simpleMessage("Merged photos"), "mlConsent": MessageLookupByLibrary.simpleMessage("Enable machine learning"), "mlConsentConfirmation": MessageLookupByLibrary.simpleMessage( @@ -1133,6 +1148,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("OpenStreetMap contributors"), "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( "Optional, as short as you like..."), + "orMergeWithExistingPerson": + MessageLookupByLibrary.simpleMessage("Or merge with existing"), "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("Or pick an existing one"), "pair": MessageLookupByLibrary.simpleMessage("Pair"), @@ -1327,7 +1344,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Reset ignored files"), "resetPasswordTitle": MessageLookupByLibrary.simpleMessage("Reset password"), - "resetPerson": MessageLookupByLibrary.simpleMessage("Reset person"), + "resetPerson": MessageLookupByLibrary.simpleMessage("Remove"), "resetToDefault": MessageLookupByLibrary.simpleMessage("Reset to default"), "restore": MessageLookupByLibrary.simpleMessage("Restore"), @@ -1338,6 +1355,7 @@ class MessageLookup extends MessageLookupByLibrary { "resumableUploads": MessageLookupByLibrary.simpleMessage("Resumable uploads"), "retry": MessageLookupByLibrary.simpleMessage("Retry"), + "review": MessageLookupByLibrary.simpleMessage("Review"), "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( "Please review and delete the items you believe are duplicates."), "reviewSuggestions": @@ -1351,6 +1369,7 @@ class MessageLookup extends MessageLookupByLibrary { "saveCollage": MessageLookupByLibrary.simpleMessage("Save collage"), "saveCopy": MessageLookupByLibrary.simpleMessage("Save copy"), "saveKey": MessageLookupByLibrary.simpleMessage("Save key"), + "savePerson": MessageLookupByLibrary.simpleMessage("Save person"), "saveYourRecoveryKeyIfYouHaventAlready": MessageLookupByLibrary.simpleMessage( "Save your recovery key if you haven\'t already"), @@ -1710,6 +1729,7 @@ class MessageLookup extends MessageLookupByLibrary { "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("View recovery key"), "viewer": MessageLookupByLibrary.simpleMessage("Viewer"), + "viewersSuccessfullyAdded": m75, "visitWebToManage": MessageLookupByLibrary.simpleMessage( "Please visit web.ente.io to manage your subscription"), "waitingForVerification": diff --git a/mobile/lib/generated/intl/messages_es.dart b/mobile/lib/generated/intl/messages_es.dart index 4722d1b979..694b232e0a 100644 --- a/mobile/lib/generated/intl/messages_es.dart +++ b/mobile/lib/generated/intl/messages_es.dart @@ -461,8 +461,6 @@ class MessageLookup extends MessageLookupByLibrary { "Revisa tu bandeja de entrada (y spam) para completar la verificación"), "checkStatus": MessageLookupByLibrary.simpleMessage("Comprobar estado"), "checking": MessageLookupByLibrary.simpleMessage("Comprobando..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( "Reclamar almacenamiento gratis"), "claimMore": MessageLookupByLibrary.simpleMessage("¡Reclama más!"), @@ -730,8 +728,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Habilitar"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente soporta aprendizaje automático en el dispositivo para la detección de caras, búsqueda mágica y otras características de búsqueda avanzada"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Activar Mapas"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Esto mostrará tus fotos en el mapa mundial.\n\nEste mapa está gestionado por Open Street Map, y la ubicación exacta de tus fotos nunca se comparte.\n\nPuedes deshabilitar esta función en cualquier momento en Ajustes."), @@ -1421,8 +1417,6 @@ class MessageLookup extends MessageLookupByLibrary { "Agrega descripciones como \"#viaje\" en la información de la foto para encontrarlas aquí rápidamente"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage("Buscar por fecha, mes o año"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Las personas se mostrarán aquí una vez que se haya hecho la indexación"), "searchFileTypesAndNamesEmptySection": @@ -1440,8 +1434,6 @@ class MessageLookup extends MessageLookupByLibrary { "Agrupar las fotos que se tomaron cerca de la localización de una foto"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Invita a gente y verás todas las fotos compartidas aquí"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Seguridad"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_et.dart b/mobile/lib/generated/intl/messages_et.dart index 2e63ce3c96..dc3a61a6ff 100644 --- a/mobile/lib/generated/intl/messages_et.dart +++ b/mobile/lib/generated/intl/messages_et.dart @@ -49,8 +49,6 @@ class MessageLookup extends MessageLookupByLibrary { "checkStatus": MessageLookupByLibrary.simpleMessage("Kontrolli staatust"), "checking": MessageLookupByLibrary.simpleMessage("Kontrollimine..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "collaborator": MessageLookupByLibrary.simpleMessage("Kaastööline"), "collectPhotos": MessageLookupByLibrary.simpleMessage("Kogu fotod"), "color": MessageLookupByLibrary.simpleMessage("Värv"), @@ -100,8 +98,6 @@ class MessageLookup extends MessageLookupByLibrary { "done": MessageLookupByLibrary.simpleMessage("Valmis"), "edit": MessageLookupByLibrary.simpleMessage("Muuda"), "email": MessageLookupByLibrary.simpleMessage("E-post"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("Krüpteerimine"), "enterCode": MessageLookupByLibrary.simpleMessage("Sisesta kood"), "enterEmail": MessageLookupByLibrary.simpleMessage("Sisesta e-post"), @@ -207,12 +203,6 @@ class MessageLookup extends MessageLookupByLibrary { "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Skaneeri seda QR koodi\noma autentimisrakendusega"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "security": MessageLookupByLibrary.simpleMessage("Turvalisus"), "selectAll": MessageLookupByLibrary.simpleMessage("Vali kõik"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Vali keel"), diff --git a/mobile/lib/generated/intl/messages_fa.dart b/mobile/lib/generated/intl/messages_fa.dart index 1971bbb14f..e0d79f058d 100644 --- a/mobile/lib/generated/intl/messages_fa.dart +++ b/mobile/lib/generated/intl/messages_fa.dart @@ -103,8 +103,6 @@ class MessageLookup extends MessageLookupByLibrary { "لطفا صندوق ورودی (و هرزنامه) خود را برای تایید کامل بررسی کنید"), "checkStatus": MessageLookupByLibrary.simpleMessage("بررسی وضعیت"), "checking": MessageLookupByLibrary.simpleMessage("در حال بررسی..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage( "پیوندی ایجاد کنید تا به افراد اجازه دهید بدون نیاز به برنامه یا حساب کاربری Ente عکس‌ها را در آلبوم اشتراک گذاشته شده شما اضافه و مشاهده کنند. برای جمع‌آوری عکس‌های رویداد عالی است."), "collaborator": MessageLookupByLibrary.simpleMessage("همکار"), @@ -170,8 +168,6 @@ class MessageLookup extends MessageLookupByLibrary { "editLocationTagTitle": MessageLookupByLibrary.simpleMessage("ویرایش مکان"), "email": MessageLookupByLibrary.simpleMessage("ایمیل"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("رمزگذاری"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("کلیدهای رمزنگاری"), @@ -326,12 +322,6 @@ class MessageLookup extends MessageLookupByLibrary { "safelyStored": MessageLookupByLibrary.simpleMessage("به طور ایمن"), "saveKey": MessageLookupByLibrary.simpleMessage("ذخیره کلید"), "search": MessageLookupByLibrary.simpleMessage("جستجو"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "security": MessageLookupByLibrary.simpleMessage("امنیت"), "selectAll": MessageLookupByLibrary.simpleMessage("انتخاب همه"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_fr.dart b/mobile/lib/generated/intl/messages_fr.dart index 8ee6ddb2fc..7769f7e65f 100644 --- a/mobile/lib/generated/intl/messages_fr.dart +++ b/mobile/lib/generated/intl/messages_fr.dart @@ -21,7 +21,7 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'fr'; static String m6(count) => - "${Intl.plural(count, zero: 'Ajouter un coauteur', one: 'Ajouter un coauteur', other: 'Ajouter des coauteurs')}"; + "${Intl.plural(count, zero: 'Ajouter un collaborateur', one: 'Ajouter un collaborateur', other: 'Ajouter des collaborateurs')}"; static String m7(count) => "${Intl.plural(count, one: 'Ajoutez un objet', other: 'Ajoutez des objets')}"; @@ -30,7 +30,7 @@ class MessageLookup extends MessageLookupByLibrary { "Votre extension de ${storageAmount} est valable jusqu\'au ${endDate}"; static String m9(count) => - "${Intl.plural(count, zero: 'Ajouter un lecteur', one: 'Ajouter un lecteur', other: 'Ajouter des lecteurs')}"; + "${Intl.plural(count, zero: 'Ajouter un observateur', one: 'Ajouter un observateur', other: 'Ajouter des observateurs')}"; static String m10(emailOrName) => "Ajouté par ${emailOrName}"; @@ -176,7 +176,7 @@ class MessageLookup extends MessageLookupByLibrary { "Code de parrainage Ente : ${referralCode} \n\nValidez le dans Paramètres → Général → Références pour obtenir ${referralStorageInGB} Go gratuitement après votre inscription à un plan payant\n\nhttps://ente.io"; static String m57(numberOfPeople) => - "${Intl.plural(numberOfPeople, zero: 'Partagez avec des personnes spécifiques', one: 'Partagé avec 1 personne', other: 'Partagé avec ${numberOfPeople} des gens')}"; + "${Intl.plural(numberOfPeople, zero: 'Partagez avec des personnes spécifiques', one: 'Partagé avec 1 personne', other: 'Partagé avec ${numberOfPeople} personnes')}"; static String m58(emailIDs) => "Partagé avec ${emailIDs}"; @@ -237,6 +237,7 @@ class MessageLookup extends MessageLookupByLibrary { "Je comprends que si je perds mon mot de passe, je perdrai mes données puisque mes données sont chiffrées de bout en bout."), "activeSessions": MessageLookupByLibrary.simpleMessage("Sessions actives"), + "add": MessageLookupByLibrary.simpleMessage("Ajouter"), "addAName": MessageLookupByLibrary.simpleMessage("Ajouter un nom"), "addANewEmail": MessageLookupByLibrary.simpleMessage("Ajouter un nouvel email"), @@ -250,7 +251,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ajouter la localisation"), "addLocationButton": MessageLookupByLibrary.simpleMessage("Ajouter"), "addMore": MessageLookupByLibrary.simpleMessage("Ajouter"), + "addName": MessageLookupByLibrary.simpleMessage("Ajouter un nom"), + "addNameOrMerge": + MessageLookupByLibrary.simpleMessage("Ajouter un nom ou fusionner"), "addNew": MessageLookupByLibrary.simpleMessage("Ajouter un nouveau"), + "addNewPerson": MessageLookupByLibrary.simpleMessage( + "Ajouter une nouvelle personne"), "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage( "Détails des modules complémentaires"), "addOnValidTill": m8, @@ -471,7 +477,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Vérifier le statut"), "checking": MessageLookupByLibrary.simpleMessage("Vérification..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Vérification des modèles..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( "Réclamer le stockage gratuit"), "claimMore": MessageLookupByLibrary.simpleMessage("Réclamez plus !"), @@ -504,7 +510,7 @@ class MessageLookup extends MessageLookupByLibrary { "codeUsedByYou": MessageLookupByLibrary.simpleMessage("Code utilisé par vous"), "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage( - "Créez un lien pour permettre aux gens d\'ajouter et de voir des photos dans votre album partagé sans avoir besoin d\'une application ente ou d\'un compte. Idéal pour récupérer des photos d\'événement."), + "Créez un lien pour permettre aux personnes d\'ajouter et de voir des photos dans votre album partagé sans avoir besoin d\'une application Ente ou d\'un compte. Idéal pour récupérer des photos d\'événement."), "collaborativeLink": MessageLookupByLibrary.simpleMessage("Lien collaboratif"), "collaborativeLinkCreatedFor": m18, @@ -523,6 +529,7 @@ class MessageLookup extends MessageLookupByLibrary { "collectPhotosDescription": MessageLookupByLibrary.simpleMessage( "Créez un lien où vos amis peuvent ajouter des photos en qualité originale."), "color": MessageLookupByLibrary.simpleMessage("Couleur "), + "configuration": MessageLookupByLibrary.simpleMessage("Paramètres"), "confirm": MessageLookupByLibrary.simpleMessage("Confirmer"), "confirm2FADisable": MessageLookupByLibrary.simpleMessage( "Voulez-vous vraiment désactiver l\'authentification à deux facteurs ?"), @@ -747,7 +754,7 @@ class MessageLookup extends MessageLookupByLibrary { "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente prend en charge l\'apprentissage automatique sur l\'appareil pour la reconnaissance faciale, la recherche magique et d\'autres fonctionnalités de recherche avancée"), "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), + "Activer l\'apprentissage automatique pour la recherche magique et la reconnaissance faciale"), "enableMaps": MessageLookupByLibrary.simpleMessage("Activer la carte"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Vos photos seront affichées sur une carte du monde.\n\nCette carte est hébergée par Open Street Map, et les emplacements exacts de vos photos ne sont jamais partagés.\n\nVous pouvez désactiver cette fonction à tout moment dans les Paramètres."), @@ -810,6 +817,10 @@ class MessageLookup extends MessageLookupByLibrary { "exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"), "exportYourData": MessageLookupByLibrary.simpleMessage("Exportez vos données"), + "extraPhotosFound": MessageLookupByLibrary.simpleMessage( + "Photos supplémentaires trouvées"), + "extraPhotosFoundFor": MessageLookupByLibrary.simpleMessage( + "Photos supplémentaires trouvées pour \$text"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Reconnaissance faciale"), "faces": MessageLookupByLibrary.simpleMessage("Visages"), @@ -1031,6 +1042,8 @@ class MessageLookup extends MessageLookupByLibrary { "loadingYourPhotos": MessageLookupByLibrary.simpleMessage("Chargement de vos photos..."), "localGallery": MessageLookupByLibrary.simpleMessage("Galerie locale"), + "localIndexing": + MessageLookupByLibrary.simpleMessage("Indexation locale"), "localSyncErrorMessage": MessageLookupByLibrary.simpleMessage( "Il semble que quelque chose s\'est mal passé car la synchronisation des photos locales prend plus de temps que prévu. Veuillez contacter notre équipe d\'assistance"), "location": MessageLookupByLibrary.simpleMessage("Emplacement"), @@ -1086,6 +1099,8 @@ class MessageLookup extends MessageLookupByLibrary { "matrix": MessageLookupByLibrary.simpleMessage("Matrix"), "memoryCount": m3, "merchandise": MessageLookupByLibrary.simpleMessage("Marchandise"), + "mergeWithExisting": + MessageLookupByLibrary.simpleMessage("Fusionner avec existant"), "mlConsent": MessageLookupByLibrary.simpleMessage( "Activer l\'apprentissage automatique"), "mlConsentConfirmation": MessageLookupByLibrary.simpleMessage( @@ -1128,6 +1143,7 @@ class MessageLookup extends MessageLookupByLibrary { "Impossible de se connecter à Ente, veuillez vérifier vos paramètres réseau et contacter le support si l\'erreur persiste."), "never": MessageLookupByLibrary.simpleMessage("Jamais"), "newAlbum": MessageLookupByLibrary.simpleMessage("Nouvel album"), + "newPerson": MessageLookupByLibrary.simpleMessage("Nouvelle personne"), "newToEnte": MessageLookupByLibrary.simpleMessage("Nouveau à Ente"), "newest": MessageLookupByLibrary.simpleMessage("Le plus récent"), "next": MessageLookupByLibrary.simpleMessage("Suivant"), @@ -1175,6 +1191,7 @@ class MessageLookup extends MessageLookupByLibrary { "onEnte": MessageLookupByLibrary.simpleMessage( "Sur ente"), "onlyFamilyAdminCanChangeCode": m43, + "onlyThem": MessageLookupByLibrary.simpleMessage("Seulement eux"), "oops": MessageLookupByLibrary.simpleMessage("Oups"), "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( "Oups, impossible d\'enregistrer les modifications"), @@ -1189,7 +1206,7 @@ class MessageLookup extends MessageLookupByLibrary { "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( "Optionnel, aussi court que vous le souhaitez..."), "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage( - "Sélectionner un fichier existant"), + "Ou sélectionner un email existant"), "pair": MessageLookupByLibrary.simpleMessage("Associer"), "pairWithPin": MessageLookupByLibrary.simpleMessage("Appairer avec le code PIN"), @@ -1231,6 +1248,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Supprimer définitivement"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "Supprimer définitivement de l\'appareil ?"), + "personName": + MessageLookupByLibrary.simpleMessage("Nom de la personne"), "photoDescriptions": MessageLookupByLibrary.simpleMessage("Descriptions de la photo"), "photoGridSize": @@ -1446,7 +1465,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Recherche par date, mois ou année"), "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), + "Les images seront affichées ici une fois le traitement terminé"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Les personnes seront affichées ici une fois l\'indexation terminée"), "searchFileTypesAndNamesEmptySection": @@ -1463,9 +1482,9 @@ class MessageLookup extends MessageLookupByLibrary { "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( "Grouper les photos qui sont prises dans un certain angle d\'une photo"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "Invitez des gens, et vous verrez ici toutes les photos qu\'ils partagent"), + "Invitez des personnes, et vous verrez ici toutes les photos qu\'elles partagent"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), + "Les personnes seront affichées ici une fois le traitement terminé"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Sécurité"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_gu.dart b/mobile/lib/generated/intl/messages_gu.dart index bc1223bae1..6c1d7e4d90 100644 --- a/mobile/lib/generated/intl/messages_gu.dart +++ b/mobile/lib/generated/intl/messages_gu.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'gu'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_he.dart b/mobile/lib/generated/intl/messages_he.dart index 0bf3a2cd5e..39b4708995 100644 --- a/mobile/lib/generated/intl/messages_he.dart +++ b/mobile/lib/generated/intl/messages_he.dart @@ -258,8 +258,6 @@ class MessageLookup extends MessageLookupByLibrary { "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "אנא בדוק את תיבת הדואר שלך (והספאם) כדי להשלים את האימות"), "checking": MessageLookupByLibrary.simpleMessage("בודק..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("תבע מקום אחסון בחינם"), "claimMore": MessageLookupByLibrary.simpleMessage("תבע עוד!"), @@ -412,8 +410,6 @@ class MessageLookup extends MessageLookupByLibrary { "emailVerificationToggle": MessageLookupByLibrary.simpleMessage("אימות מייל"), "empty": MessageLookupByLibrary.simpleMessage("ריק"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("הצפנה"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("מפתחות ההצפנה"), "endtoendEncryptedByDefault": MessageLookupByLibrary.simpleMessage( @@ -744,12 +740,6 @@ class MessageLookup extends MessageLookupByLibrary { "סרוק את הברקוד הזה\nבעזרת אפליקציית האימות שלך"), "searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("שם האלבום"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "security": MessageLookupByLibrary.simpleMessage("אבטחה"), "selectAlbum": MessageLookupByLibrary.simpleMessage("בחר אלבום"), "selectAll": MessageLookupByLibrary.simpleMessage("בחר הכל"), diff --git a/mobile/lib/generated/intl/messages_hi.dart b/mobile/lib/generated/intl/messages_hi.dart index 9b895a3bce..ff4756d8d4 100644 --- a/mobile/lib/generated/intl/messages_hi.dart +++ b/mobile/lib/generated/intl/messages_hi.dart @@ -28,8 +28,6 @@ class MessageLookup extends MessageLookupByLibrary { "askDeleteReason": MessageLookupByLibrary.simpleMessage( "आपका अकाउंट हटाने का मुख्य कारण क्या है?"), "cancel": MessageLookupByLibrary.simpleMessage("रद्द करें"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage( "अकाउंट डिलीट करने की पुष्टि करें"), "confirmPassword": @@ -58,8 +56,6 @@ class MessageLookup extends MessageLookupByLibrary { "deleteRequestSLAText": MessageLookupByLibrary.simpleMessage( "आपका अनुरोध 72 घंटों के भीतर संसाधित किया जाएगा।"), "email": MessageLookupByLibrary.simpleMessage("ईमेल"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "entePhotosPerm": MessageLookupByLibrary.simpleMessage( "Ente को आपकी तस्वीरों को संरक्षित करने के लिए अनुमति की आवश्यकता है"), "enterValidEmail": MessageLookupByLibrary.simpleMessage( @@ -89,12 +85,6 @@ class MessageLookup extends MessageLookupByLibrary { "recoverButton": MessageLookupByLibrary.simpleMessage("पुनः प्राप्त"), "recoverySuccessful": MessageLookupByLibrary.simpleMessage("रिकवरी सफल हुई!"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "selectReason": MessageLookupByLibrary.simpleMessage("कारण चुनें"), "sendEmail": MessageLookupByLibrary.simpleMessage("ईमेल भेजें"), "somethingWentWrongPleaseTryAgain": diff --git a/mobile/lib/generated/intl/messages_id.dart b/mobile/lib/generated/intl/messages_id.dart index 16cc01c400..139df05f05 100644 --- a/mobile/lib/generated/intl/messages_id.dart +++ b/mobile/lib/generated/intl/messages_id.dart @@ -399,8 +399,6 @@ class MessageLookup extends MessageLookupByLibrary { "Silakan periksa kotak masuk (serta kotak spam) untuk menyelesaikan verifikasi"), "checkStatus": MessageLookupByLibrary.simpleMessage("Periksa status"), "checking": MessageLookupByLibrary.simpleMessage("Memeriksa..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Peroleh kuota gratis"), "claimMore": @@ -614,8 +612,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Verifikasi email"), "empty": MessageLookupByLibrary.simpleMessage("Kosongkan"), "emptyTrash": MessageLookupByLibrary.simpleMessage("Kosongkan sampah?"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Aktifkan Peta"), "encryptingBackup": MessageLookupByLibrary.simpleMessage("Mengenkripsi cadangan..."), @@ -1179,8 +1175,6 @@ class MessageLookup extends MessageLookupByLibrary { "Tambah keterangan seperti \"#trip\" pada info foto agar mudah ditemukan di sini"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Telusuri dengan tanggal, bulan, atau tahun"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Orang akan ditampilkan di sini setelah pengindeksan selesai"), "searchFileTypesAndNamesEmptySection": @@ -1191,10 +1185,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Album, nama dan jenis file"), "searchHint5": MessageLookupByLibrary.simpleMessage( "Segera tiba: Penelusuran wajah & ajaib ✨"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Keamanan"), "selectALocation": MessageLookupByLibrary.simpleMessage("Pilih lokasi"), diff --git a/mobile/lib/generated/intl/messages_it.dart b/mobile/lib/generated/intl/messages_it.dart index fba3207714..17c0ebe4c1 100644 --- a/mobile/lib/generated/intl/messages_it.dart +++ b/mobile/lib/generated/intl/messages_it.dart @@ -468,7 +468,7 @@ class MessageLookup extends MessageLookupByLibrary { "checking": MessageLookupByLibrary.simpleMessage("Controllo in corso..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Verifica dei modelli..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Richiedi spazio gratuito"), "claimMore": MessageLookupByLibrary.simpleMessage("Richiedine di più!"), @@ -734,8 +734,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Abilita"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente supporta l\'apprendimento automatico eseguito sul dispositivo per il riconoscimento dei volti, la ricerca magica e altre funzioni di ricerca avanzata"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Abilita le Mappe"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Questo mostrerà le tue foto su una mappa del mondo.\n\nQuesta mappa è ospitata da Open Street Map e le posizioni esatte delle tue foto non sono mai condivise.\n\nPuoi disabilitare questa funzionalità in qualsiasi momento, dalle Impostazioni."), @@ -1092,7 +1090,7 @@ class MessageLookup extends MessageLookupByLibrary { "moderateStrength": MessageLookupByLibrary.simpleMessage("Mediocre"), "modifyYourQueryOrTrySearchingFor": MessageLookupByLibrary.simpleMessage( - "Modifica la tua interrogazione o prova a cercare"), + "Modifica la tua ricerca o prova con"), "moments": MessageLookupByLibrary.simpleMessage("Momenti"), "monthly": MessageLookupByLibrary.simpleMessage("Mensile"), "moreDetails": MessageLookupByLibrary.simpleMessage("Più dettagli"), @@ -1427,8 +1425,6 @@ class MessageLookup extends MessageLookupByLibrary { "Aggiungi descrizioni come \"#viaggio\" nelle informazioni delle foto per trovarle rapidamente qui"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Ricerca per data, mese o anno"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Le persone saranno mostrate qui una volta completata l\'indicizzazione"), "searchFileTypesAndNamesEmptySection": @@ -1446,8 +1442,6 @@ class MessageLookup extends MessageLookupByLibrary { "Raggruppa foto scattate entro un certo raggio da una foto"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Invita persone e vedrai qui tutte le foto condivise da loro"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Sicurezza"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_ja.dart b/mobile/lib/generated/intl/messages_ja.dart index aca65c20cd..407926a11e 100644 --- a/mobile/lib/generated/intl/messages_ja.dart +++ b/mobile/lib/generated/intl/messages_ja.dart @@ -404,8 +404,6 @@ class MessageLookup extends MessageLookupByLibrary { "メールボックスを確認してEメールの所有を証明してください(見つからない場合は、スパムの中も確認してください)"), "checkStatus": MessageLookupByLibrary.simpleMessage("ステータスの確認"), "checking": MessageLookupByLibrary.simpleMessage("確認中…"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("無料のストレージを受け取る"), "claimMore": MessageLookupByLibrary.simpleMessage("もっと!"), @@ -623,8 +621,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("有効化"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Enteは顔認識、マジック検索、その他の高度な検索機能のため、あなたのデバイス上で機械学習をしています"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("マップを有効にする"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "世界地図上にあなたの写真を表示します。\n\n地図はOpenStreetMapを利用しており、あなたの写真の位置情報が外部に共有されることはありません。\n\nこの機能は設定から無効にすることができます"), @@ -1204,8 +1200,6 @@ class MessageLookup extends MessageLookupByLibrary { "写真情報に \"#trip\" のように説明を追加すれば、ここで簡単に見つけることができます"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage("日付、月または年で検索"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage("学習が完了すると、ここに人が表示されます"), "searchFileTypesAndNamesEmptySection": @@ -1220,8 +1214,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("当時の直近で撮影された写真をグループ化"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage("友達を招待すると、共有される写真はここから閲覧できます"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("セキュリティ"), "selectALocation": MessageLookupByLibrary.simpleMessage("場所を選択"), diff --git a/mobile/lib/generated/intl/messages_km.dart b/mobile/lib/generated/intl/messages_km.dart index 2c2c8454a6..22d4231361 100644 --- a/mobile/lib/generated/intl/messages_km.dart +++ b/mobile/lib/generated/intl/messages_km.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'km'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_ko.dart b/mobile/lib/generated/intl/messages_ko.dart index f04fded67c..e378d62fd9 100644 --- a/mobile/lib/generated/intl/messages_ko.dart +++ b/mobile/lib/generated/intl/messages_ko.dart @@ -27,16 +27,12 @@ class MessageLookup extends MessageLookupByLibrary { "askDeleteReason": MessageLookupByLibrary.simpleMessage("계정을 삭제하는 가장 큰 이유가 무엇인가요?"), "cancel": MessageLookupByLibrary.simpleMessage("닫기"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("계정 삭제 확인"), "deleteAccount": MessageLookupByLibrary.simpleMessage("계정 삭제"), "deleteAccountPermanentlyButton": MessageLookupByLibrary.simpleMessage("계정을 영구적으로 삭제"), "email": MessageLookupByLibrary.simpleMessage("이메일"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enterValidEmail": MessageLookupByLibrary.simpleMessage("올바른 이메일 주소를 입력하세요."), "enterYourEmailAddress": @@ -44,12 +40,6 @@ class MessageLookup extends MessageLookupByLibrary { "feedback": MessageLookupByLibrary.simpleMessage("피드백"), "invalidEmailAddress": MessageLookupByLibrary.simpleMessage("잘못된 이메일 주소"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "verify": MessageLookupByLibrary.simpleMessage("인증"), "yourAccountHasBeenDeleted": MessageLookupByLibrary.simpleMessage("계정이 삭제되었습니다.") diff --git a/mobile/lib/generated/intl/messages_lt.dart b/mobile/lib/generated/intl/messages_lt.dart index 5976a2645f..851ae30cec 100644 --- a/mobile/lib/generated/intl/messages_lt.dart +++ b/mobile/lib/generated/intl/messages_lt.dart @@ -241,7 +241,7 @@ class MessageLookup extends MessageLookupByLibrary { "checkStatus": MessageLookupByLibrary.simpleMessage("Tikrinti būseną"), "checking": MessageLookupByLibrary.simpleMessage("Tikrinama..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Tikrinami modeliai..."), "cleanUncategorized": MessageLookupByLibrary.simpleMessage("Valyti nekategorizuotą"), "cleanUncategorizedDescription": MessageLookupByLibrary.simpleMessage( @@ -412,8 +412,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Įjungti"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "„Ente“ palaiko įrenginyje mašininį mokymąsi, skirtą veidų atpažinimui, magiškai paieškai ir kitoms išplėstinėms paieškos funkcijoms"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Tai parodys jūsų nuotraukas pasaulio žemėlapyje.\n\nŠį žemėlapį talpina „OpenStreetMap“, o tiksliomis nuotraukų vietovėmis niekada nebendrinama.\n\nŠią funkciją bet kada galite išjungti iš nustatymų."), "enabled": MessageLookupByLibrary.simpleMessage("Įjungta"), @@ -553,6 +551,8 @@ class MessageLookup extends MessageLookupByLibrary { "linkNeverExpires": MessageLookupByLibrary.simpleMessage("Niekada"), "loadingGallery": MessageLookupByLibrary.simpleMessage("Įkeliama galerija..."), + "loadingModel": + MessageLookupByLibrary.simpleMessage("Atsisiunčiami modeliai..."), "loadingYourPhotos": MessageLookupByLibrary.simpleMessage("Įkeliamos nuotraukos..."), "localGallery": @@ -826,15 +826,9 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Skenuokite šį QR kodą\nsu autentifikatoriaus programa"), "search": MessageLookupByLibrary.simpleMessage("Ieškoti"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchHint4": MessageLookupByLibrary.simpleMessage("Vietovė"), "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( "Grupės nuotraukos, kurios padarytos tam tikru spinduliu nuo nuotraukos"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "selectALocation": MessageLookupByLibrary.simpleMessage("Pasirinkite vietovę"), diff --git a/mobile/lib/generated/intl/messages_nl.dart b/mobile/lib/generated/intl/messages_nl.dart index 9024f6817f..a24091114a 100644 --- a/mobile/lib/generated/intl/messages_nl.dart +++ b/mobile/lib/generated/intl/messages_nl.dart @@ -238,6 +238,7 @@ class MessageLookup extends MessageLookupByLibrary { "Ik begrijp dat als ik mijn wachtwoord verlies, ik mijn gegevens kan verliezen omdat mijn gegevens end-to-end versleuteld zijn."), "activeSessions": MessageLookupByLibrary.simpleMessage("Actieve sessies"), + "add": MessageLookupByLibrary.simpleMessage("Toevoegen"), "addAName": MessageLookupByLibrary.simpleMessage("Een naam toevoegen"), "addANewEmail": MessageLookupByLibrary.simpleMessage("Nieuw e-mailadres toevoegen"), @@ -251,7 +252,12 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Locatie toevoegen"), "addLocationButton": MessageLookupByLibrary.simpleMessage("Toevoegen"), "addMore": MessageLookupByLibrary.simpleMessage("Meer toevoegen"), + "addName": MessageLookupByLibrary.simpleMessage("Naam toevoegen"), + "addNameOrMerge": MessageLookupByLibrary.simpleMessage( + "Naam toevoegen of samenvoegen"), "addNew": MessageLookupByLibrary.simpleMessage("Nieuwe toevoegen"), + "addNewPerson": + MessageLookupByLibrary.simpleMessage("Nieuw persoon toevoegen"), "addOnPageSubtitle": MessageLookupByLibrary.simpleMessage("Details van add-ons"), "addOnValidTill": m8, @@ -289,6 +295,8 @@ class MessageLookup extends MessageLookupByLibrary { "allClear": MessageLookupByLibrary.simpleMessage("✨ Alles in orde"), "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage("Alle herinneringen bewaard"), + "allPersonGroupingWillReset": MessageLookupByLibrary.simpleMessage( + "Alle groepen voor deze persoon worden gereset, en je verliest alle suggesties die voor deze persoon zijn gedaan"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Sta toe dat mensen met de link ook foto\'s kunnen toevoegen aan het gedeelde album."), "allowAddingPhotos": @@ -345,6 +353,9 @@ class MessageLookup extends MessageLookupByLibrary { "Weet je zeker dat je wilt uitloggen?"), "areYouSureYouWantToRenew": MessageLookupByLibrary.simpleMessage( "Weet u zeker dat u wilt verlengen?"), + "areYouSureYouWantToResetThisPerson": + MessageLookupByLibrary.simpleMessage( + "Weet u zeker dat u deze persoon wilt resetten?"), "askCancelReason": MessageLookupByLibrary.simpleMessage( "Uw abonnement is opgezegd. Wilt u de reden delen?"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( @@ -459,7 +470,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Status controleren"), "checking": MessageLookupByLibrary.simpleMessage("Controleren..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Modellen controleren..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Claim gratis opslag"), "claimMore": MessageLookupByLibrary.simpleMessage("Claim meer!"), @@ -509,6 +520,7 @@ class MessageLookup extends MessageLookupByLibrary { "collectPhotosDescription": MessageLookupByLibrary.simpleMessage( "Maak een link waarin je vrienden foto\'s kunnen uploaden in de originele kwaliteit."), "color": MessageLookupByLibrary.simpleMessage("Kleur"), + "configuration": MessageLookupByLibrary.simpleMessage("Configuratie"), "confirm": MessageLookupByLibrary.simpleMessage("Bevestig"), "confirm2FADisable": MessageLookupByLibrary.simpleMessage( "Weet u zeker dat u tweestapsverificatie wilt uitschakelen?"), @@ -726,8 +738,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Inschakelen"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente ondersteunt on-device machine learning voor gezichtsherkenning, magisch zoeken en andere geavanceerde zoekfuncties"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Kaarten inschakelen"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( @@ -793,6 +803,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Logboek exporteren"), "exportYourData": MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"), + "extraPhotosFound": + MessageLookupByLibrary.simpleMessage("Extra foto\'s gevonden"), + "extraPhotosFoundFor": MessageLookupByLibrary.simpleMessage( + "Extra foto\'s gevonden voor \$text"), "faceRecognition": MessageLookupByLibrary.simpleMessage("Gezichtsherkenning"), "faces": MessageLookupByLibrary.simpleMessage("Gezichten"), @@ -1007,6 +1021,8 @@ class MessageLookup extends MessageLookupByLibrary { "loadingYourPhotos": MessageLookupByLibrary.simpleMessage( "Je foto\'s worden geladen..."), "localGallery": MessageLookupByLibrary.simpleMessage("Lokale galerij"), + "localIndexing": + MessageLookupByLibrary.simpleMessage("Lokaal indexeren"), "localSyncErrorMessage": MessageLookupByLibrary.simpleMessage( "Het lijkt erop dat er iets mis is gegaan omdat het synchroniseren van lokale foto\'s meer tijd kost dan verwacht. Neem contact op met ons supportteam"), "location": MessageLookupByLibrary.simpleMessage("Locatie"), @@ -1061,6 +1077,8 @@ class MessageLookup extends MessageLookupByLibrary { "matrix": MessageLookupByLibrary.simpleMessage("Matrix"), "memoryCount": m3, "merchandise": MessageLookupByLibrary.simpleMessage("Merchandise"), + "mergeWithExisting": + MessageLookupByLibrary.simpleMessage("Samenvoegen met bestaand"), "mlConsent": MessageLookupByLibrary.simpleMessage("Schakel machine learning in"), "mlConsentConfirmation": MessageLookupByLibrary.simpleMessage( @@ -1102,6 +1120,7 @@ class MessageLookup extends MessageLookupByLibrary { "Kan geen verbinding maken met Ente, controleer uw netwerkinstellingen en neem contact op met ondersteuning als de fout zich blijft voordoen."), "never": MessageLookupByLibrary.simpleMessage("Nooit"), "newAlbum": MessageLookupByLibrary.simpleMessage("Nieuw album"), + "newPerson": MessageLookupByLibrary.simpleMessage("Nieuw persoon"), "newToEnte": MessageLookupByLibrary.simpleMessage("Nieuw bij Ente"), "newest": MessageLookupByLibrary.simpleMessage("Nieuwste"), "next": MessageLookupByLibrary.simpleMessage("Volgende"), @@ -1150,6 +1169,7 @@ class MessageLookup extends MessageLookupByLibrary { "onEnte": MessageLookupByLibrary.simpleMessage( "Op ente"), "onlyFamilyAdminCanChangeCode": m43, + "onlyThem": MessageLookupByLibrary.simpleMessage("Alleen hen"), "oops": MessageLookupByLibrary.simpleMessage("Oeps"), "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( "Oeps, kon bewerkingen niet opslaan"), @@ -1203,6 +1223,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Permanent verwijderen"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "Permanent verwijderen van apparaat?"), + "personName": MessageLookupByLibrary.simpleMessage("Naam van persoon"), "photoDescriptions": MessageLookupByLibrary.simpleMessage("Foto beschrijvingen"), "photoGridSize": @@ -1366,6 +1387,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Reset genegeerde bestanden"), "resetPasswordTitle": MessageLookupByLibrary.simpleMessage("Wachtwoord resetten"), + "resetPerson": MessageLookupByLibrary.simpleMessage("Reset persoon"), "resetToDefault": MessageLookupByLibrary.simpleMessage( "Standaardinstellingen herstellen"), "restore": MessageLookupByLibrary.simpleMessage("Herstellen"), @@ -1411,8 +1433,6 @@ class MessageLookup extends MessageLookupByLibrary { "Voeg beschrijvingen zoals \"#weekendje weg\" toe in foto-info om ze snel hier te vinden"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Zoeken op een datum, maand of jaar"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Mensen worden hier getoond als het indexeren klaar is"), "searchFileTypesAndNamesEmptySection": @@ -1430,8 +1450,6 @@ class MessageLookup extends MessageLookupByLibrary { "Foto\'s groeperen die in een bepaalde straal van een foto worden genomen"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Nodig mensen uit, en je ziet alle foto\'s die door hen worden gedeeld hier"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Beveiliging"), "selectALocation": @@ -1792,6 +1810,8 @@ class MessageLookup extends MessageLookupByLibrary { "yesLogout": MessageLookupByLibrary.simpleMessage("Ja, log uit"), "yesRemove": MessageLookupByLibrary.simpleMessage("Ja, verwijderen"), "yesRenew": MessageLookupByLibrary.simpleMessage("Ja, verlengen"), + "yesResetPerson": + MessageLookupByLibrary.simpleMessage("Ja, reset persoon"), "you": MessageLookupByLibrary.simpleMessage("Jij"), "youAreOnAFamilyPlan": MessageLookupByLibrary.simpleMessage( "U bent onderdeel van een familie abonnement!"), diff --git a/mobile/lib/generated/intl/messages_no.dart b/mobile/lib/generated/intl/messages_no.dart index f4fc74d775..913213386a 100644 --- a/mobile/lib/generated/intl/messages_no.dart +++ b/mobile/lib/generated/intl/messages_no.dart @@ -117,8 +117,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Endre tillatelser?"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Sjekk innboksen din (og spam) for å fullføre verifikasjonen"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "clearIndexes": MessageLookupByLibrary.simpleMessage("Tøm indekser"), "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "Kode kopiert til utklippstavlen"), @@ -189,8 +187,6 @@ class MessageLookup extends MessageLookupByLibrary { "dropSupportEmail": m25, "duplicateItemsGroup": m27, "email": MessageLookupByLibrary.simpleMessage("E-post"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("Kryptering"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("Krypteringsnøkkel"), @@ -352,12 +348,6 @@ class MessageLookup extends MessageLookupByLibrary { "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Skann denne strekkoden med\nautentiseringsappen din"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "security": MessageLookupByLibrary.simpleMessage("Sikkerhet"), "selectAll": MessageLookupByLibrary.simpleMessage("Velg alle"), "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_pl.dart b/mobile/lib/generated/intl/messages_pl.dart index d4cf46daee..284584bebd 100644 --- a/mobile/lib/generated/intl/messages_pl.dart +++ b/mobile/lib/generated/intl/messages_pl.dart @@ -21,7 +21,7 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'pl'; static String m6(count) => - "${Intl.plural(count, one: 'Dodaj współuczestnika', few: 'Dodaj współuczestników', many: 'Dodaj współuczestników', other: 'Dodaj współuczestników')}"; + "${Intl.plural(count, one: 'Dodaj współuczestnika', few: 'Dodaj współuczestników', other: 'Dodaj współuczestników')}"; static String m7(count) => "${Intl.plural(count, one: 'Dodaj element', few: 'Dodaj elementy', other: 'Dodaj elementów')}"; @@ -30,7 +30,7 @@ class MessageLookup extends MessageLookupByLibrary { "Twój dodatek ${storageAmount} jest ważny do ${endDate}"; static String m9(count) => - "${Intl.plural(count, one: 'Dodaj widza', few: 'Dodaj widzów', many: 'Dodaj widzów', other: 'Dodaj widzów')}"; + "${Intl.plural(count, one: 'Dodaj widza', few: 'Dodaj widzów', other: 'Dodaj widzów')}"; static String m10(emailOrName) => "Dodane przez ${emailOrName}"; @@ -470,8 +470,6 @@ class MessageLookup extends MessageLookupByLibrary { "Sprawdź swoją skrzynkę odbiorczą (i spam), aby zakończyć weryfikację"), "checkStatus": MessageLookupByLibrary.simpleMessage("Sprawdź stan"), "checking": MessageLookupByLibrary.simpleMessage("Sprawdzanie..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( "Odbierz bezpłatną przestrzeń dyskową"), "claimMore": MessageLookupByLibrary.simpleMessage("Zdobądź więcej!"), @@ -732,8 +730,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Włącz"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente obsługuje uczenie maszynowe na urządzeniu dla rozpoznawania twarzy, wyszukiwania magicznego i innych zaawansowanych funkcji wyszukiwania"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Włącz mapy"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "To pokaże Twoje zdjęcia na mapie świata.\n\nTa mapa jest hostowana przez Open Street Map, a dokładne lokalizacje Twoich zdjęć nigdy nie są udostępniane.\n\nMożesz wyłączyć tę funkcję w każdej chwili w ustawieniach."), @@ -1167,6 +1163,7 @@ class MessageLookup extends MessageLookupByLibrary { "onEnte": MessageLookupByLibrary.simpleMessage("W ente"), "onlyFamilyAdminCanChangeCode": m43, + "onlyThem": MessageLookupByLibrary.simpleMessage("Tylko te"), "oops": MessageLookupByLibrary.simpleMessage("Ups"), "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( "Ups, nie udało się zapisać zmian"), @@ -1427,8 +1424,6 @@ class MessageLookup extends MessageLookupByLibrary { "Dodaj opisy takie jak \"#trip\" w informacji o zdjęciu, aby szybko znaleźć je tutaj"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Szukaj według daty, miesiąca lub roku"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Po zakończeniu indeksowania ludzie będą tu wyświetlani"), "searchFileTypesAndNamesEmptySection": @@ -1446,8 +1441,6 @@ class MessageLookup extends MessageLookupByLibrary { "Grupuj zdjęcia zrobione w promieniu zdjęcia"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Zaproś ludzi, a zobaczysz tutaj wszystkie udostępnione przez nich zdjęcia"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Bezpieczeństwo"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_pt.dart b/mobile/lib/generated/intl/messages_pt.dart index e4859e76ff..6cb173a708 100644 --- a/mobile/lib/generated/intl/messages_pt.dart +++ b/mobile/lib/generated/intl/messages_pt.dart @@ -465,7 +465,7 @@ class MessageLookup extends MessageLookupByLibrary { "checkStatus": MessageLookupByLibrary.simpleMessage("Verificar estado"), "checking": MessageLookupByLibrary.simpleMessage("Verificando..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Verificando modelos..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( "Reivindicar armazenamento grátis"), "claimMore": MessageLookupByLibrary.simpleMessage("Reivindique mais!"), @@ -730,7 +730,7 @@ class MessageLookup extends MessageLookupByLibrary { "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente suporta aprendizagem de máquina para reconhecimento facial, busca mágica e outros recursos de busca avançados"), "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), + "Ativar aprendizagem de máquina para busca mágica e reconhecimento facial"), "enableMaps": MessageLookupByLibrary.simpleMessage("Ativar mapas"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Isso exibirá suas fotos em um mapa mundial.\n\nEste mapa é hospedado por Open Street Map, e as exatas localizações das fotos nunca serão compartilhadas.\n\nVocê pode desativar esta função a qualquer momento em Opções."), @@ -1422,7 +1422,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage("Buscar por data, mês ou ano"), "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), + "As imagens serão exibidas aqui quando o processamento for concluído"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "As pessoas apareceram aqui quando a indexação for concluída"), "searchFileTypesAndNamesEmptySection": @@ -1441,7 +1441,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Convide pessoas e você verá todas as fotos compartilhadas por elas aqui"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), + "As pessoas serão exibidas aqui quando o processamento for concluído"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Segurança"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_ro.dart b/mobile/lib/generated/intl/messages_ro.dart index a170cb9d76..befa98a7e9 100644 --- a/mobile/lib/generated/intl/messages_ro.dart +++ b/mobile/lib/generated/intl/messages_ro.dart @@ -378,8 +378,6 @@ class MessageLookup extends MessageLookupByLibrary { "checkStatus": MessageLookupByLibrary.simpleMessage("Verificați starea"), "checking": MessageLookupByLibrary.simpleMessage("Se verifică..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Revendică spațiul gratuit"), "claimMore": @@ -605,8 +603,6 @@ class MessageLookup extends MessageLookupByLibrary { "emptyTrash": MessageLookupByLibrary.simpleMessage("Goliți coșul de gunoi?"), "enable": MessageLookupByLibrary.simpleMessage("Activare"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enabled": MessageLookupByLibrary.simpleMessage("Activat"), "encryption": MessageLookupByLibrary.simpleMessage("Criptarea"), "encryptionKeys": @@ -1162,8 +1158,6 @@ class MessageLookup extends MessageLookupByLibrary { "Adăugați descrieri precum „#excursie” în informațiile fotografiilor pentru a le găsi ușor aici"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Căutare după o dată, o lună sau un an"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Persoanele vor fi afișate aici odată ce indexarea este finalizată"), "searchFileTypesAndNamesEmptySection": @@ -1182,8 +1176,6 @@ class MessageLookup extends MessageLookupByLibrary { "Grupare fotografii realizate în raza unei fotografii"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Invitați persoane și veți vedea aici toate fotografiile distribuite de acestea"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Securitate"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_ru.dart b/mobile/lib/generated/intl/messages_ru.dart index a1eff0f9c7..b2a11da5f0 100644 --- a/mobile/lib/generated/intl/messages_ru.dart +++ b/mobile/lib/generated/intl/messages_ru.dart @@ -442,8 +442,6 @@ class MessageLookup extends MessageLookupByLibrary { "Пожалуйста, проверьте свой почтовый ящик (и спам) для завершения верификации"), "checkStatus": MessageLookupByLibrary.simpleMessage("Проверить статус"), "checking": MessageLookupByLibrary.simpleMessage("Проверка..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( "Получить бесплатное хранилище"), "claimMore": MessageLookupByLibrary.simpleMessage("Получите больше!"), @@ -681,8 +679,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("Включить"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente поддерживает машинное обучение на устройстве для распознавания лиц, умного поиска и других расширенных функций поиска"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Включить карты"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Ваши фотографии будут показаны на карте мира.\n\nЭта карта размещена на Open Street Map, и точное местоположение ваших фотографий никогда не разглашается.\n\nВы можете отключить эту функцию в любое время в настройках."), @@ -1353,8 +1349,6 @@ class MessageLookup extends MessageLookupByLibrary { "Добавьте описания типа \"#поездка\" в информацию о фото и быстро найдите их здесь"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Поиск по дате, месяцу или году"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Люди будут показаны здесь, как только будет выполнено индексирование"), "searchFileTypesAndNamesEmptySection": @@ -1372,8 +1366,6 @@ class MessageLookup extends MessageLookupByLibrary { "Групповые фотографии, сделанные в некотором радиусе от фотографии"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Пригласите людей, и вы увидите все фотографии, которыми они поделились здесь"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Безопасность"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_sl.dart b/mobile/lib/generated/intl/messages_sl.dart index 81b60bd891..d41d848b0f 100644 --- a/mobile/lib/generated/intl/messages_sl.dart +++ b/mobile/lib/generated/intl/messages_sl.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'sl'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_sv.dart b/mobile/lib/generated/intl/messages_sv.dart index 0e26a61abe..186d1f8dc1 100644 --- a/mobile/lib/generated/intl/messages_sv.dart +++ b/mobile/lib/generated/intl/messages_sv.dart @@ -158,8 +158,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ändra behörighet?"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Kontrollera din inkorg (och skräppost) för att slutföra verifieringen"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimed": MessageLookupByLibrary.simpleMessage("Nyttjad"), "close": MessageLookupByLibrary.simpleMessage("Stäng"), "codeAppliedPageTitle": @@ -244,8 +242,6 @@ class MessageLookup extends MessageLookupByLibrary { "edit": MessageLookupByLibrary.simpleMessage("Redigera"), "email": MessageLookupByLibrary.simpleMessage("E-post"), "emailNoEnteAccount": m29, - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "encryption": MessageLookupByLibrary.simpleMessage("Kryptering"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("Krypteringsnycklar"), @@ -457,14 +453,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Album"), "searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("Albumnamn"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFileTypesAndNamesEmptySection": MessageLookupByLibrary.simpleMessage("Filtyper och namn"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "selectAlbum": MessageLookupByLibrary.simpleMessage("Välj album"), "selectLanguage": MessageLookupByLibrary.simpleMessage("Välj språk"), diff --git a/mobile/lib/generated/intl/messages_ta.dart b/mobile/lib/generated/intl/messages_ta.dart index 9141faf514..30c00c6d72 100644 --- a/mobile/lib/generated/intl/messages_ta.dart +++ b/mobile/lib/generated/intl/messages_ta.dart @@ -27,8 +27,6 @@ class MessageLookup extends MessageLookupByLibrary { "askDeleteReason": MessageLookupByLibrary.simpleMessage( "உங்கள் கணக்கை நீக்குவதற்கான முக்கிய காரணம் என்ன?"), "cancel": MessageLookupByLibrary.simpleMessage("ரத்து செய்"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage( "கணக்கு நீக்குதலை உறுதிப்படுத்தவும்"), "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( @@ -41,8 +39,6 @@ class MessageLookup extends MessageLookupByLibrary { "deleteReason1": MessageLookupByLibrary.simpleMessage( "எனக்கு தேவையான ஒரு முக்கிய அம்சம் இதில் இல்லை"), "email": MessageLookupByLibrary.simpleMessage("மின்னஞ்சல்"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enterValidEmail": MessageLookupByLibrary.simpleMessage( "சரியான மின்னஞ்சல் முகவரியை உள்ளிடவும்."), "enterYourEmailAddress": MessageLookupByLibrary.simpleMessage( @@ -52,12 +48,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("தவறான மின்னஞ்சல் முகவரி"), "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage( "இந்த தகவலுடன் தயவுசெய்து எங்களுக்கு உதவுங்கள்"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "verify": MessageLookupByLibrary.simpleMessage("சரிபார்க்கவும்") }; } diff --git a/mobile/lib/generated/intl/messages_te.dart b/mobile/lib/generated/intl/messages_te.dart index 055e89f535..5e415c9da0 100644 --- a/mobile/lib/generated/intl/messages_te.dart +++ b/mobile/lib/generated/intl/messages_te.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'te'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_th.dart b/mobile/lib/generated/intl/messages_th.dart index 1aa1e4bf69..93c45e7af6 100644 --- a/mobile/lib/generated/intl/messages_th.dart +++ b/mobile/lib/generated/intl/messages_th.dart @@ -92,8 +92,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("เปลี่ยนรหัสผ่าน"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "โปรดตรวจสอบกล่องจดหมาย (และสแปม) ของคุณ เพื่อยืนยันให้เสร็จสิ้น"), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "คัดลอกรหัสไปยังคลิปบอร์ดแล้ว"), "collectPhotos": MessageLookupByLibrary.simpleMessage("รวบรวมรูปภาพ"), @@ -151,8 +149,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("แก้ไขตำแหน่ง"), "eligible": MessageLookupByLibrary.simpleMessage("มีสิทธิ์"), "email": MessageLookupByLibrary.simpleMessage("อีเมล"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("เปิดใช้งานแผนที่"), "encryption": MessageLookupByLibrary.simpleMessage("การเข้ารหัส"), "enterCode": MessageLookupByLibrary.simpleMessage("ป้อนรหัส"), @@ -288,12 +284,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "บันทึกคีย์การกู้คืนของคุณหากคุณยังไม่ได้ทำ"), "scanCode": MessageLookupByLibrary.simpleMessage("สแกนรหัส"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "selectAll": MessageLookupByLibrary.simpleMessage("เลือกทั้งหมด"), "selectReason": MessageLookupByLibrary.simpleMessage("เลือกเหตุผล"), "sendEmail": MessageLookupByLibrary.simpleMessage("ส่งอีเมล"), diff --git a/mobile/lib/generated/intl/messages_ti.dart b/mobile/lib/generated/intl/messages_ti.dart index 7761d43d9b..775cc78213 100644 --- a/mobile/lib/generated/intl/messages_ti.dart +++ b/mobile/lib/generated/intl/messages_ti.dart @@ -21,16 +21,5 @@ class MessageLookup extends MessageLookupByLibrary { String get localeName => 'ti'; final messages = _notInlinedMessages(_notInlinedMessages); - static Map _notInlinedMessages(_) => { - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), - "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete") - }; + static Map _notInlinedMessages(_) => {}; } diff --git a/mobile/lib/generated/intl/messages_tr.dart b/mobile/lib/generated/intl/messages_tr.dart index 0f27c79538..dc2571fa97 100644 --- a/mobile/lib/generated/intl/messages_tr.dart +++ b/mobile/lib/generated/intl/messages_tr.dart @@ -389,8 +389,6 @@ class MessageLookup extends MessageLookupByLibrary { "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Lütfen doğrulama işlemini tamamlamak için gelen kutunuzu (ve spam klasörünüzü) kontrol edin"), "checking": MessageLookupByLibrary.simpleMessage("Kontrol ediliyor..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Bedava alan talep edin"), "claimMore": MessageLookupByLibrary.simpleMessage("Arttır!"), @@ -606,8 +604,6 @@ class MessageLookup extends MessageLookupByLibrary { "empty": MessageLookupByLibrary.simpleMessage("Boşalt"), "emptyTrash": MessageLookupByLibrary.simpleMessage("Çöp kutusu boşaltılsın mı?"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("Haritaları Etkinleştir"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( @@ -1167,8 +1163,6 @@ class MessageLookup extends MessageLookupByLibrary { "Fotoğraf bilgilerini burada hızlı bir şekilde bulmak için \"#trip\" gibi açıklamalar ekleyin"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Tarihe, aya veya yıla göre arama yapın"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFileTypesAndNamesEmptySection": MessageLookupByLibrary.simpleMessage("Dosya türleri ve adları"), "searchHint1": @@ -1184,8 +1178,6 @@ class MessageLookup extends MessageLookupByLibrary { "Bir fotoğrafın belli bir yarıçapında çekilen fotoğrafları gruplandırın"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "İnsanları davet ettiğinizde onların paylaştığı tüm fotoğrafları burada göreceksiniz"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Güvenlik"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_uk.dart b/mobile/lib/generated/intl/messages_uk.dart index 78a875268c..4f3331d456 100644 --- a/mobile/lib/generated/intl/messages_uk.dart +++ b/mobile/lib/generated/intl/messages_uk.dart @@ -465,7 +465,7 @@ class MessageLookup extends MessageLookupByLibrary { "checkStatus": MessageLookupByLibrary.simpleMessage("Перевірити стан"), "checking": MessageLookupByLibrary.simpleMessage("Перевірка..."), "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), + MessageLookupByLibrary.simpleMessage("Перевірка моделей..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Отримайте безплатне сховище"), "claimMore": MessageLookupByLibrary.simpleMessage("Отримайте більше!"), @@ -732,7 +732,7 @@ class MessageLookup extends MessageLookupByLibrary { "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente підтримує машинне навчання для розпізнавання обличчя, магічний пошук та інші розширені функції пошуку"), "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), + "Увімкніть машинне навчання для магічного пошуку та розпізнавання облич"), "enableMaps": MessageLookupByLibrary.simpleMessage("Увімкнути мапи"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "Це покаже ваші фотографії на мапі світу.\n\nЦя мапа розміщена на OpenStreetMap, і точне розташування ваших фотографій ніколи не розголошується.\n\nВи можете будь-коли вимкнути цю функцію в налаштуваннях."), @@ -1162,6 +1162,7 @@ class MessageLookup extends MessageLookupByLibrary { "onEnte": MessageLookupByLibrary.simpleMessage("В Ente"), "onlyFamilyAdminCanChangeCode": m43, + "onlyThem": MessageLookupByLibrary.simpleMessage("Тільки вони"), "oops": MessageLookupByLibrary.simpleMessage("От халепа"), "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( "Ой, не вдалося зберегти зміни"), @@ -1432,7 +1433,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( "Шукати за датою, місяцем або роком"), "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), + "Зображення будуть показані тут після завершення обробки"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( "Люди будуть показані тут після завершення індексації"), "searchFileTypesAndNamesEmptySection": @@ -1450,7 +1451,7 @@ class MessageLookup extends MessageLookupByLibrary { "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( "Запросіть людей, і ви побачите всі фотографії, якими вони поділилися, тут"), "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), + "Люди будуть показані тут після завершення обробки"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("Безпека"), "selectALocation": diff --git a/mobile/lib/generated/intl/messages_zh.dart b/mobile/lib/generated/intl/messages_zh.dart index 1f8f49fc33..f4e41bed32 100644 --- a/mobile/lib/generated/intl/messages_zh.dart +++ b/mobile/lib/generated/intl/messages_zh.dart @@ -394,8 +394,6 @@ class MessageLookup extends MessageLookupByLibrary { "请检查您的收件箱 (或者是在您的“垃圾邮件”列表内) 以完成验证"), "checkStatus": MessageLookupByLibrary.simpleMessage("检查状态"), "checking": MessageLookupByLibrary.simpleMessage("正在检查..."), - "checkingModels": - MessageLookupByLibrary.simpleMessage("Checking models..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("领取免费存储"), "claimMore": MessageLookupByLibrary.simpleMessage("领取更多!"), "claimed": MessageLookupByLibrary.simpleMessage("已领取"), @@ -599,8 +597,6 @@ class MessageLookup extends MessageLookupByLibrary { "enable": MessageLookupByLibrary.simpleMessage("启用"), "enableMLIndexingDesc": MessageLookupByLibrary.simpleMessage( "Ente 支持设备上的机器学习,实现人脸识别、魔法搜索和其他高级搜索功能"), - "enableMachineLearningBanner": MessageLookupByLibrary.simpleMessage( - "Enable machine learning for magic search and face recognition"), "enableMaps": MessageLookupByLibrary.simpleMessage("启用地图"), "enableMapsDesc": MessageLookupByLibrary.simpleMessage( "这将在世界地图上显示您的照片。\n\n该地图由 Open Street Map 托管,并且您的照片的确切位置永远不会共享。\n\n您可以随时从“设置”中禁用此功能。"), @@ -1137,8 +1133,6 @@ class MessageLookup extends MessageLookupByLibrary { "在照片信息中添加“#旅游”等描述,以便在此处快速找到它们"), "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage("按日期搜索,月份或年份"), - "searchDiscoverEmptySection": MessageLookupByLibrary.simpleMessage( - "Images will be shown here once processing is complete"), "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage("待索引完成后,人物将显示在此处"), "searchFileTypesAndNamesEmptySection": @@ -1152,8 +1146,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("在照片的一定半径内拍摄的几组照片"), "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage("邀请他人,您将在此看到他们分享的所有照片"), - "searchPersonsEmptySection": MessageLookupByLibrary.simpleMessage( - "People will be shown here once processing is complete"), "searchResultCount": m53, "security": MessageLookupByLibrary.simpleMessage("安全"), "selectALocation": MessageLookupByLibrary.simpleMessage("选择一个位置"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index a76cc59628..fd46104264 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -8849,6 +8849,17 @@ class S { ); } + /// `Find them quickly` + String get findThemQuickly { + return Intl.message( + 'Find them quickly', + name: 'findThemQuickly', + desc: + 'Subtitle to indicate that the user can find people quickly by name', + args: [], + ); + } + /// `Find people quickly by name` String get findPeopleByName { return Intl.message( @@ -8995,6 +9006,76 @@ class S { ); } + /// `Enter name` + String get enterName { + return Intl.message( + 'Enter name', + name: 'enterName', + desc: '', + args: [], + ); + } + + /// `Save person` + String get savePerson { + return Intl.message( + 'Save person', + name: 'savePerson', + desc: '', + args: [], + ); + } + + /// `Edit person` + String get editPerson { + return Intl.message( + 'Edit person', + name: 'editPerson', + desc: '', + args: [], + ); + } + + /// `Merged photos` + String get mergedPhotos { + return Intl.message( + 'Merged photos', + name: 'mergedPhotos', + desc: '', + args: [], + ); + } + + /// `Or merge with existing` + String get orMergeWithExistingPerson { + return Intl.message( + 'Or merge with existing', + name: 'orMergeWithExistingPerson', + desc: '', + args: [], + ); + } + + /// `Birthday (optional)` + String get enterDateOfBirth { + return Intl.message( + 'Birthday (optional)', + name: 'enterDateOfBirth', + desc: '', + args: [], + ); + } + + /// `Birthday` + String get birthday { + return Intl.message( + 'Birthday', + name: 'birthday', + desc: '', + args: [], + ); + } + /// `Remove person label` String get removePersonLabel { return Intl.message( @@ -9255,6 +9336,16 @@ class S { ); } + /// `Review` + String get review { + return Intl.message( + 'Review', + name: 'review', + desc: '', + args: [], + ); + } + /// `Use as cover` String get useAsCover { return Intl.message( @@ -9856,10 +9947,10 @@ class S { ); } - /// `Reset person` + /// `Remove` String get resetPerson { return Intl.message( - 'Reset person', + 'Remove', name: 'resetPerson', desc: '', args: [], @@ -9945,6 +10036,32 @@ class S { args: [], ); } + + /// `{count, plural, =0 {Added 0 viewer} =1 {Added 1 viewer} other {Added {count} viewers}}` + String viewersSuccessfullyAdded(int count) { + return Intl.plural( + count, + zero: 'Added 0 viewer', + one: 'Added 1 viewer', + other: 'Added $count viewers', + name: 'viewersSuccessfullyAdded', + desc: 'Number of viewers that were successfully added to an album.', + args: [count], + ); + } + + /// `{count, plural, =0 {Added 0 collaborator} =1 {Added 1 collaborator} other {Added {count} collaborators}}` + String collaboratorsSuccessfullyAdded(int count) { + return Intl.plural( + count, + zero: 'Added 0 collaborator', + one: 'Added 1 collaborator', + other: 'Added $count collaborators', + name: 'collaboratorsSuccessfullyAdded', + desc: 'Number of collaborators that were successfully added to an album.', + args: [count], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 2c7b8701fd..8e0c22ed47 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1246,6 +1246,10 @@ "locations": "Locations", "descriptions": "Descriptions", "addAName": "Add a name", + "findThemQuickly": "Find them quickly", + "@findThemQuickly": { + "description": "Subtitle to indicate that the user can find people quickly by name" + }, "findPeopleByName": "Find people quickly by name", "addViewers": "{count, plural, zero {Add viewer} one {Add viewer} other {Add viewers}}", "addCollaborators": "{count, plural, zero {Add collaborator} one {Add collaborator} other {Add collaborators}}", @@ -1260,6 +1264,13 @@ "createCollaborativeLink": "Create collaborative link", "search": "Search", "enterPersonName": "Enter person name", + "enterName": "Enter name", + "savePerson": "Save person", + "editPerson": "Edit person", + "mergedPhotos": "Merged photos", + "orMergeWithExistingPerson" : "Or merge with existing", + "enterDateOfBirth": "Birthday (optional)", + "birthday": "Birthday", "removePersonLabel": "Remove person label", "autoPairDesc": "Auto pair works only with devices that support Chromecast.", "manualPairDesc": "Pair with PIN works with any screen you wish to view your album on.", @@ -1286,6 +1297,7 @@ "right": "Right", "whatsNew": "What's new", "reviewSuggestions": "Review suggestions", + "review": "Review", "useAsCover": "Use as cover", "notPersonLabel": "Not {name}?", "@notPersonLabel": { @@ -1355,7 +1367,7 @@ "extraPhotosFound": "Extra photos found", "configuration": "Configuration", "localIndexing": "Local indexing", - "resetPerson": "Reset person", + "resetPerson": "Remove", "areYouSureYouWantToResetThisPerson": "Are you sure you want to reset this person?", "allPersonGroupingWillReset": "All groupings for this person will be reset, and you will lose all suggestions made for this person", "yesResetPerson": "Yes, reset person", @@ -1363,5 +1375,25 @@ "checkingModels": "Checking models...", "enableMachineLearningBanner": "Enable machine learning for magic search and face recognition", "searchDiscoverEmptySection": "Images will be shown here once processing is complete", - "searchPersonsEmptySection": "People will be shown here once processing is complete" + "searchPersonsEmptySection": "People will be shown here once processing is complete", + "viewersSuccessfullyAdded": "{count, plural, =0 {Added 0 viewer} =1 {Added 1 viewer} other {Added {count} viewers}}", + "@viewersSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of viewers that were successfully added to an album." + }, + "collaboratorsSuccessfullyAdded": "{count, plural, =0 {Added 0 collaborator} =1 {Added 1 collaborator} other {Added {count} collaborators}}", + "@collaboratorsSuccessfullyAdded": { + "placeholders": { + "count": { + "type": "int", + "example": "2" + } + }, + "description": "Number of collaborators that were successfully added to an album." + } } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pl.arb b/mobile/lib/l10n/intl_pl.arb index ab62d77bda..c1b699f32b 100644 --- a/mobile/lib/l10n/intl_pl.arb +++ b/mobile/lib/l10n/intl_pl.arb @@ -1247,8 +1247,8 @@ "descriptions": "Opisy", "addAName": "Dodaj nazwę", "findPeopleByName": "Szybko szukaj osób po imieniu", - "addViewers": "{count, plural, one {Dodaj widza} few {Dodaj widzów} many {Dodaj widzów} other {Dodaj widzów}}", - "addCollaborators": "{count, plural, one {Dodaj współuczestnika} few {Dodaj współuczestników} many {Dodaj współuczestników} other {Dodaj współuczestników}}", + "addViewers": "{count, plural, one {Dodaj widza} few {Dodaj widzów} other {Dodaj widzów}}", + "addCollaborators": "{count, plural, one {Dodaj współuczestnika} few {Dodaj współuczestników} other {Dodaj współuczestników}}", "longPressAnEmailToVerifyEndToEndEncryption": "Naciśnij i przytrzymaj e-mail, aby zweryfikować szyfrowanie end-to-end.", "developerSettingsWarning": "Czy na pewno chcesz zmodyfikować ustawienia programisty?", "developerSettings": "Ustawienia dla programistów", diff --git a/mobile/lib/models/ml/face/person.dart b/mobile/lib/models/ml/face/person.dart index 2e5191c485..3757246b80 100644 --- a/mobile/lib/models/ml/face/person.dart +++ b/mobile/lib/models/ml/face/person.dart @@ -51,8 +51,12 @@ class PersonData { final bool isHidden; String? avatarFaceID; List? assigned = List.empty(); - List? rejected = List.empty(); + List? rejectedFaceIDs = List.empty(); final String? birthDate; + // email should be always looked via userID as user might have changed + // their email ids. + final String? email; + final int? userID; bool hasAvatar() => avatarFaceID != null; @@ -62,10 +66,12 @@ class PersonData { PersonData({ required this.name, this.assigned, - this.rejected, + this.rejectedFaceIDs, this.avatarFaceID, this.isHidden = false, this.birthDate, + this.email, + this.userID, }); // copyWith PersonData copyWith({ @@ -75,13 +81,17 @@ class PersonData { bool? isHidden, int? version, String? birthDate, + String? email, + int? userID, }) { return PersonData( name: name ?? this.name, assigned: assigned ?? this.assigned, - avatarFaceID: avatarFaceId ?? this.avatarFaceID, + avatarFaceID: avatarFaceId ?? avatarFaceID, isHidden: isHidden ?? this.isHidden, birthDate: birthDate ?? this.birthDate, + email: email ?? this.email, + userID: userID ?? this.userID, ); } @@ -95,7 +105,7 @@ class PersonData { assignedCount += a.faces.length; } sb.writeln('Assigned: ${assigned?.length} withFaces $assignedCount'); - sb.writeln('Rejected: ${rejected?.length}'); + sb.writeln('Rejected faceIDs: ${rejectedFaceIDs?.length}'); if (assigned != null) { for (var cluster in assigned!) { sb.writeln('Cluster: ${cluster.id} - ${cluster.faces.length}'); @@ -108,10 +118,12 @@ class PersonData { Map toJson() => { 'name': name, 'assigned': assigned?.map((e) => e.toJson()).toList(), - 'rejected': rejected?.map((e) => e.toJson()).toList(), + 'rejectedFaceIDs': rejectedFaceIDs, 'avatarFaceID': avatarFaceID, 'isHidden': isHidden, 'birthDate': birthDate, + 'email': email, + 'userID': userID, }; // fromJson @@ -122,18 +134,21 @@ class PersonData { json['assigned'].map((x) => ClusterInfo.fromJson(x)), ); - final rejected = (json['rejected'] == null || json['rejected'].length == 0) - ? [] - : List.from( - json['rejected'].map((x) => ClusterInfo.fromJson(x)), - ); + final List rejectedFaceIDs = + (json['rejectedFaceIDs'] == null || json['rejectedFaceIDs'].length == 0) + ? [] + : List.from( + json['rejectedFaceIDs'], + ); return PersonData( name: json['name'] as String, assigned: assigned, - rejected: rejected, + rejectedFaceIDs: rejectedFaceIDs, avatarFaceID: json['avatarFaceID'] as String?, isHidden: json['isHidden'] as bool? ?? false, birthDate: json['birthDate'] as String?, + userID: json['userID'] as int?, + email: json['email'] as String?, ); } } diff --git a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart index ebb16f2252..9ad92ebb5f 100644 --- a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart @@ -22,6 +22,7 @@ class FaceInfo { final bool? badFace; final Vector? vEmbedding; String? clusterId; + final List? rejectedClusterIds; String? closestFaceId; int? closestDist; int? fileCreationTime; @@ -32,6 +33,7 @@ class FaceInfo { this.badFace, this.vEmbedding, this.clusterId, + this.rejectedClusterIds, this.fileCreationTime, }); } @@ -161,7 +163,7 @@ class FaceClusteringService extends SuperIsolate { _logger.info( 'Running complete clustering on ${input.length} faces with distance threshold $mergeThreshold', ); - final ClusteringResult clusterResult = await predictCompleteComputer( + final ClusteringResult clusterResult = await _predictCompleteComputer( input, fileIDToCreationTime: fileIDToCreationTime, oldClusterSummaries: oldClusterSummaries, @@ -173,7 +175,7 @@ class FaceClusteringService extends SuperIsolate { _logger.info( 'Running linear clustering on ${input.length} faces with distance threshold $distanceThreshold', ); - final ClusteringResult clusterResult = await predictLinearComputer( + final ClusteringResult clusterResult = await _predictLinearComputer( input, fileIDToCreationTime: fileIDToCreationTime, oldClusterSummaries: oldClusterSummaries, @@ -188,7 +190,7 @@ class FaceClusteringService extends SuperIsolate { } /// Runs the clustering algorithm [runLinearClustering] on the given [input], in computer, without any dynamic thresholding - Future predictLinearComputer( + Future _predictLinearComputer( Map input, { Map? fileIDToCreationTime, required Map oldClusterSummaries, @@ -248,7 +250,7 @@ class FaceClusteringService extends SuperIsolate { /// Runs the clustering algorithm [_runCompleteClustering] on the given [input], in computer. /// /// WARNING: Only use on small datasets, as it is not optimized for large datasets. - Future predictCompleteComputer( + Future _predictCompleteComputer( Map input, { Map? fileIDToCreationTime, required Map oldClusterSummaries, @@ -328,6 +330,7 @@ ClusteringResult runLinearClustering(Map args) { dtype: DType.float32, ), clusterId: face.clusterId, + rejectedClusterIds: face.rejectedClusterIds, fileCreationTime: fileIDToCreationTime?[getFileIdFromFaceId(face.faceID)], ), @@ -372,7 +375,6 @@ ClusteringResult runLinearClustering(Map args) { _logger.info( "[ClusterIsolate] ${DateTime.now()} Processing $totalFaces faces ($newToClusterCount new, $alreadyClusteredCount already done) in total in this round ${offset != null ? "on top of ${offset + facesWithClusterID.length} earlier processed faces" : ""}", ); - // set current epoch time as clusterID String clusterID = newClusterID(); if (facesWithClusterID.isEmpty) { // assign a clusterID to the first face @@ -398,6 +400,7 @@ ClusteringResult runLinearClustering(Map args) { } else { thresholdValue = distanceThreshold; } + final bool faceHasBeenRejectedBefore = sortedFaceInfos[i].rejectedClusterIds != null; if (i % 250 == 0) { _logger.info("Processed ${offset != null ? i + offset : i} faces"); } @@ -410,6 +413,13 @@ ClusteringResult runLinearClustering(Map args) { distance > conservativeDistanceThreshold) { continue; } + if (faceHasBeenRejectedBefore && + sortedFaceInfos[j].clusterId != null && + sortedFaceInfos[i].rejectedClusterIds!.contains( + sortedFaceInfos[j].clusterId!, + )) { + continue; + } closestDistance = distance; closestIdx = j; } diff --git a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart index 1822d3fb55..6726761415 100644 --- a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart +++ b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart @@ -3,6 +3,7 @@ import "dart:typed_data" show Uint8List; class FaceDbInfoForClustering { final String faceID; String? clusterId; + List? rejectedClusterIds; final Uint8List embeddingBytes; final double faceScore; final double blurValue; diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 2f17010180..8e9171611c 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -334,6 +334,31 @@ class ClusterFeedbackService { return true; } + Future addClusterToExistingPerson({ + required PersonEntity person, + required String clusterID, + }) async { + if (person.data.rejectedFaceIDs != null && + person.data.rejectedFaceIDs!.isNotEmpty) { + final clusterFaceIDs = + await MLDataDB.instance.getFaceIDsForCluster(clusterID); + final rejectedLengthBefore = person.data.rejectedFaceIDs!.length; + person.data.rejectedFaceIDs! + .removeWhere((faceID) => clusterFaceIDs.contains(faceID)); + final rejectedLengthAfter = person.data.rejectedFaceIDs!.length; + if (rejectedLengthBefore != rejectedLengthAfter) { + _logger.info( + 'Removed ${rejectedLengthBefore - rejectedLengthAfter} rejected faces from person ${person.data.name} due to adding cluster $clusterID', + ); + await PersonService.instance.updatePerson(person); + } + } + await MLDataDB.instance.assignClusterToPerson( + personID: person.remoteID, + clusterID: clusterID, + ); + } + Future ignoreCluster(String clusterID) async { await PersonService.instance.addPerson('', clusterID, isHidden: true); Bus.instance.fire(PeopleChangedEvent()); diff --git a/mobile/lib/services/machine_learning/face_ml/person/person_service.dart b/mobile/lib/services/machine_learning/face_ml/person/person_service.dart index 91c0c8d087..78b1f4fcc5 100644 --- a/mobile/lib/services/machine_learning/face_ml/person/person_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/person/person_service.dart @@ -183,6 +183,11 @@ class PersonService { }) async { final person = (await getPerson(personID))!; final personData = person.data; + final clusterInfo = personData.assigned!.firstWhere( + (element) => element.id == clusterID, + ); + personData.rejectedFaceIDs ??= []; + personData.rejectedFaceIDs!.addAll(clusterInfo.faces); personData.assigned!.removeWhere((element) => element.id != clusterID); await entityService.addOrUpdate( EntityType.cgroup, @@ -201,6 +206,8 @@ class PersonService { required Set faceIDs, }) async { final personData = person.data; + + // Remove faces from clusters final List emptiedClusters = []; for (final cluster in personData.assigned!) { cluster.faces.removeWhere((faceID) => faceIDs.contains(faceID)); @@ -219,6 +226,10 @@ class PersonService { ); } + // Add removed faces to rejected faces + personData.rejectedFaceIDs ??= []; + personData.rejectedFaceIDs!.addAll(faceIDs); + await entityService.addOrUpdate( EntityType.cgroup, personData.toJson(), @@ -271,9 +282,16 @@ class PersonService { entities.sort((a, b) => a.updatedAt.compareTo(b.updatedAt)); final Map faceIdToClusterID = {}; final Map clusterToPersonID = {}; + bool shouldCheckRejectedFaces = false; for (var e in entities) { final personData = PersonData.fromJson(json.decode(e.data)); + if (personData.rejectedFaceIDs != null && + personData.rejectedFaceIDs!.isNotEmpty) { + shouldCheckRejectedFaces = true; + } int faceCount = 0; + + // Locally store the assignment of faces to clusters and people for (var cluster in personData.assigned!) { faceCount += cluster.faces.length; for (var faceId in cluster.faces) { @@ -303,10 +321,62 @@ class PersonService { logger.info("Storing feedback for ${faceIdToClusterID.length} faces"); await faceMLDataDB.updateFaceIdToClusterId(faceIdToClusterID); await faceMLDataDB.bulkAssignClusterToPersonID(clusterToPersonID); + + if (shouldCheckRejectedFaces) { + final dbPeopleClusterInfo = + await faceMLDataDB.getPersonToClusterIdToFaceIds(); + for (var e in entities) { + final personData = PersonData.fromJson(json.decode(e.data)); + if (personData.rejectedFaceIDs != null && + personData.rejectedFaceIDs!.isNotEmpty) { + final personFaceIDs = + dbPeopleClusterInfo[e.id]!.values.expand((e) => e).toSet(); + final rejectedFaceIDsSet = personData.rejectedFaceIDs!.toSet(); + final assignedAndRejectedFaceIDs = + rejectedFaceIDsSet.intersection(personFaceIDs); + + if (assignedAndRejectedFaceIDs.isNotEmpty) { + // Check that we don't have any empty clusters now + final dbPersonClusterInfo = dbPeopleClusterInfo[e.id]!; + final faceToClusterToRemove = {}; + for (final clusterIdToFaceIDs in dbPersonClusterInfo.entries) { + final clusterID = clusterIdToFaceIDs.key; + final faceIDs = clusterIdToFaceIDs.value; + final foundRejectedFacesToCluster = {}; + final removeFaceIDs = {}; + for (final faceID in faceIDs) { + if (assignedAndRejectedFaceIDs.contains(faceID)) { + removeFaceIDs.add(faceID); + foundRejectedFacesToCluster[faceID] = clusterID; + } + } + if (faceIDs.length == removeFaceIDs.length) { + logger.info( + "Cluster $clusterID for person ${e.id} ${personData.name} is empty due to rejected faces from remote, removing the cluster from person", + ); + await faceMLDataDB.removeClusterToPerson( + personID: e.id, + clusterID: clusterID, + ); + await faceMLDataDB.captureNotPersonFeedback( + personID: e.id, + clusterID: clusterID, + ); + } else { + faceToClusterToRemove.addAll(foundRejectedFacesToCluster); + } + } + // Remove the clusterID for the remaining conflicting faces + await faceMLDataDB.removeFaceIdToClusterId(faceToClusterToRemove); + } + } + } + } + return changed; } - Future updateAvatar(PersonEntity p, EnteFile file) async { + Future updateAvatar(PersonEntity p, EnteFile file) async { final Face? face = await MLDataDB.instance.getCoverFaceForPerson( recentFileID: file.uploadedFileID!, personID: p.remoteID, @@ -321,10 +391,11 @@ class PersonService { final updatedPerson = person.copyWith( data: person.data.copyWith(avatarFaceId: face.faceID), ); - await _updatePerson(updatedPerson); + await updatePerson(updatedPerson); + return updatedPerson; } - Future updateAttributes( + Future updateAttributes( String id, { String? name, String? avatarFaceId, @@ -342,10 +413,11 @@ class PersonService { birthDate: birthDate, ), ); - await _updatePerson(updatedPerson); + await updatePerson(updatedPerson); + return updatedPerson; } - Future _updatePerson(PersonEntity updatePerson) async { + Future updatePerson(PersonEntity updatePerson) async { await entityService.addOrUpdate( EntityType.cgroup, updatePerson.data.toJson(), diff --git a/mobile/lib/services/machine_learning/ml_service.dart b/mobile/lib/services/machine_learning/ml_service.dart index a9f300d74c..929730b3a6 100644 --- a/mobile/lib/services/machine_learning/ml_service.dart +++ b/mobile/lib/services/machine_learning/ml_service.dart @@ -250,6 +250,19 @@ class MLService { _logger.info('Pulling remote feedback before actually clustering'); await PersonService.instance.fetchRemoteClusterFeedback(); + final persons = await PersonService.instance.getPersons(); + final faceIdNotToCluster = >{}; + for (final person in persons) { + if (person.data.rejectedFaceIDs != null && + person.data.rejectedFaceIDs!.isNotEmpty) { + final personClusters = person.data.assigned?.map((e) => e.id).toList(); + if (personClusters != null) { + for (final faceID in person.data.rejectedFaceIDs!) { + faceIdNotToCluster[faceID] = personClusters; + } + } + } + } try { _showClusteringIsHappening = true; @@ -271,6 +284,9 @@ class MLService { if (!fileIDToCreationTime.containsKey(faceInfo.fileID)) { missingFileIDs.add(faceInfo.fileID); } else { + if (faceIdNotToCluster.containsKey(faceInfo.faceID)) { + faceInfo.rejectedClusterIds = faceIdNotToCluster[faceInfo.faceID]; + } allFaceInfoForClustering.add(faceInfo); } } diff --git a/mobile/lib/ui/common/date_input.dart b/mobile/lib/ui/common/date_input.dart new file mode 100644 index 0000000000..41fbccb76a --- /dev/null +++ b/mobile/lib/ui/common/date_input.dart @@ -0,0 +1,184 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import "package:photos/l10n/l10n.dart"; +import "package:photos/theme/ente_theme.dart"; + +class DatePickerField extends StatefulWidget { + final String? initialValue; + final String? hintText; + final void Function(DateTime?)? onChanged; + final DateTime? firstDate; + final DateTime? lastDate; + final bool isRequired; // New parameter for optional/required state + + const DatePickerField({ + super.key, + this.initialValue, + this.hintText, + this.onChanged, + this.firstDate, + this.lastDate, + this.isRequired = true, // Default to required for backward compatibility + }); + + @override + State createState() => _DatePickerFieldState(); +} + +class _DatePickerFieldState extends State { + final TextEditingController _controller = TextEditingController(); + DateTime? _selectedDate; + bool _hasError = false; + bool isUSLocale = false; + + @override + void initState() { + super.initState(); + if (widget.initialValue != null) { + _controller.text = widget.initialValue!; + _tryParseDate(widget.initialValue!, initialParse: true).ignore(); + } + } + + Future _tryParseDate(String value, {bool initialParse = false}) async { + Locale? locale = await getLocale(); + locale ??= const Locale('en', 'US'); + isUSLocale = locale.toString().toLowerCase().contains('us'); + // If the field is empty and not required, reset error state and clear date + if (value.isEmpty && !widget.isRequired) { + setState(() { + _selectedDate = null; + _hasError = false; + }); + widget.onChanged?.call(null); + return; + } + + // Skip validation for empty optional fields + if (value.isEmpty) { + return; + } + + try { + // Try parsing different date formats + DateTime? parsed; + final List formats = isUSLocale + ? [ + 'MM/dd/yyyy', + 'MM-dd-yyyy', + 'yyyy-MM-dd', // Corrected format + ] + : [ + 'dd/MM/yyyy', + 'dd-MM-yyyy', + 'yyyy-MM-dd', // Corrected format + ]; + + for (String format in formats) { + try { + parsed = DateFormat(format).parseStrict(value); + break; + } catch (_) { + continue; + } + } + + if (parsed != null) { + // Validate date range if specified + bool isValid = true; + if (widget.firstDate != null && parsed.isBefore(widget.firstDate!)) { + isValid = false; + } + if (widget.lastDate != null && parsed.isAfter(widget.lastDate!)) { + isValid = false; + } + + setState(() { + _selectedDate = isValid ? parsed : null; + _hasError = !isValid; + }); + + if (isValid) { + if (initialParse) { + _controller.text = isUSLocale + ? DateFormat('MM-dd-yyyy').format(parsed) + : DateFormat('dd-MM-yyyy').format(parsed); + } + widget.onChanged?.call(parsed); + } + } else { + setState(() { + _selectedDate = null; + _hasError = true; + }); + } + } catch (e) { + setState(() { + _selectedDate = null; + _hasError = true; + }); + } + } + + Future _showDatePicker() async { + final DateTime? picked = await showDatePicker( + context: context, + initialDate: _selectedDate ?? DateTime.now(), + firstDate: widget.firstDate ?? DateTime(1900), + lastDate: widget.lastDate ?? DateTime(2100), + ); + + if (picked != null) { + setState(() { + _selectedDate = picked; + _hasError = false; + _controller.text = isUSLocale + ? DateFormat('MM-dd-yyyy').format(picked) + : DateFormat('dd-MM-yyyy').format(picked); + }); + widget.onChanged?.call(picked); + } + } + + @override + Widget build(BuildContext context) { + return TextFormField( + controller: _controller, + onChanged: (value) => _tryParseDate(value), + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide( + color: getEnteColorScheme(context).strokeMuted, + ), + ), + fillColor: getEnteColorScheme(context).fillFaint, + filled: true, + hintText: widget.hintText ?? + "Enter date (DD/MM/YYYY)${widget.isRequired ? '' : ' (optional)'}", + hintStyle: getEnteTextTheme(context).bodyFaint, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + suffixIcon: IconButton( + icon: const Icon(Icons.calendar_today), + onPressed: _showDatePicker, + color: _hasError + ? getEnteColorScheme(context).warning500 + : getEnteColorScheme(context).strokeMuted, + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } +} diff --git a/mobile/lib/ui/common/email_input.dart b/mobile/lib/ui/common/email_input.dart new file mode 100644 index 0000000000..9fab4d44c5 --- /dev/null +++ b/mobile/lib/ui/common/email_input.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/api/collection/user.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/captioned_text_widget.dart"; +import "package:photos/ui/components/divider_widget.dart"; +import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import "package:photos/ui/sharing/user_avator_widget.dart"; + +class EmailInputField extends StatelessWidget { + final List suggestions; + final ValueChanged? onChanged; + final ValueChanged? onSelected; + final String? initialValue; + + const EmailInputField({ + super.key, + required this.suggestions, + this.onChanged, + this.onSelected, + this.initialValue, + }); + + @override + Widget build(BuildContext context) { + return Autocomplete( + initialValue: + initialValue != null ? TextEditingValue(text: initialValue!) : null, + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text == '') { + return const Iterable.empty(); + } + return suggestions.where((String option) { + return option + .toLowerCase() + .contains(textEditingValue.text.toLowerCase()); + }); + }, + onSelected: onSelected, + fieldViewBuilder: ( + BuildContext context, + TextEditingController controller, + FocusNode focusNode, + VoidCallback onFieldSubmitted, + ) { + return TextFormField( + controller: controller, + focusNode: focusNode, + onChanged: onChanged, + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + borderSide: + BorderSide(color: getEnteColorScheme(context).strokeMuted), + ), + fillColor: getEnteColorScheme(context).fillFaint, + filled: true, + hintText: S.of(context).enterEmail, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + ), + ); + }, + optionsViewBuilder: ( + BuildContext context, + AutocompleteOnSelected onSelected, + Iterable options, + ) { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(8), + child: Container( + constraints: const BoxConstraints(maxHeight: 160), + width: MediaQuery.of(context).size.width - + 16, // Adjust padding as needed + child: ListView.builder( + padding: EdgeInsets.zero, + physics: const ClampingScrollPhysics(), + shrinkWrap: true, + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final String option = options.elementAt(index); + return Column( + children: [ + MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: option, + ), + leadingIconSize: 24.0, + leadingIconWidget: UserAvatarWidget( + User(email: option, id: option.hashCode), + ), + menuItemColor: getEnteColorScheme(context).fillFaint, + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: null, + onTap: () async { + onSelected(option); + }, + isTopBorderRadiusRemoved: index > 0, + isBottomBorderRadiusRemoved: index < (options.length), + ), + (index == (options.length - 1)) + ? const SizedBox.shrink() + : DividerWidget( + dividerType: DividerType.menu, + bgColor: getEnteColorScheme(context).fillFaint, + ), + ], + ); + }, + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/mobile/lib/ui/sharing/add_participant_page.dart b/mobile/lib/ui/sharing/add_participant_page.dart index 98963bd974..4fadaf1e78 100644 --- a/mobile/lib/ui/sharing/add_participant_page.dart +++ b/mobile/lib/ui/sharing/add_participant_page.dart @@ -239,7 +239,7 @@ class _AddParticipantPage extends State { results.where((e) => e).length; showToast( context, - "Added $noOfSuccessfullAdds ${widget.isAddingViewer ? "viewers" : "collaborators"}", + widget.isAddingViewer ? S.of(context).viewersSuccessfullyAdded(noOfSuccessfullAdds) : S.of(context).collaboratorsSuccessfullyAdded(noOfSuccessfullAdds), ); if (!results.any((e) => e == false) && mounted) { diff --git a/mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart b/mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart index 106130af98..4f88ac0256 100644 --- a/mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart +++ b/mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart @@ -1,4 +1,5 @@ import "dart:async"; + import "package:flutter/material.dart"; import "package:logging/logging.dart"; import "package:photos/core/event_bus.dart"; @@ -176,36 +177,25 @@ class _HierarchicalSearchGalleryState extends State { thumbnailFallback: false, ), actionIcon: Icons.add_outlined, - text: S.of(context).addAName, - subText: S.of(context).findPeopleByName, + text: S.of(context).savePerson, + subText: S.of(context).findThemQuickly, onTap: () async { final result = await showAssignPersonAction( context, clusterID: _firstUnnamedAppliedFaceFilter!.clusterId!, ); - if (result != null && - result is (PersonEntity, EnteFile)) { - Navigator.of(context).pop(); - unawaited( - routeToPage( - context, - PeoplePage( - person: result.$1, - searchResult: null, - ), - ), - ); - } else if (result != null && - result is PersonEntity) { - Navigator.of(context).pop(); - unawaited( - routeToPage( - context, - PeoplePage( - person: result, - searchResult: null, - ), + Navigator.of(context).pop(); + if (result != null) { + final person = result is (PersonEntity, EnteFile) + ? result.$1 + : result; + // ignore: unawaited_futures + routeToPage( + context, + PeoplePage( + person: person, + searchResult: null, ), ); } diff --git a/mobile/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart b/mobile/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart new file mode 100644 index 0000000000..4a2d070abb --- /dev/null +++ b/mobile/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart @@ -0,0 +1,194 @@ +import "dart:math"; + +import "package:flutter/material.dart"; +import "package:modal_bottom_sheet/modal_bottom_sheet.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/file_load_result.dart"; +import "package:photos/models/ml/face/person.dart"; +import "package:photos/models/selected_files.dart"; +import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; +import "package:photos/services/search_service.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/bottom_of_title_bar_widget.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/ui/components/title_bar_title_widget.dart"; +import "package:photos/ui/viewer/gallery/gallery.dart"; +import "package:photos/ui/viewer/gallery/state/gallery_files_inherited_widget.dart"; + +Future showPersonAvatarPhotoSheet( + BuildContext context, + PersonEntity person, +) async { + return await showBarModalBottomSheet( + context: context, + builder: (context) { + return PickPersonCoverPhotoWidget(person); + }, + shape: const RoundedRectangleBorder( + side: BorderSide(width: 0), + borderRadius: BorderRadius.vertical( + top: Radius.circular(5), + ), + ), + topControl: const SizedBox.shrink(), + backgroundColor: getEnteColorScheme(context).backgroundElevated, + barrierColor: backdropFaintDark, + enableDrag: false, + ); +} + +class PickPersonCoverPhotoWidget extends StatelessWidget { + final PersonEntity personEntity; + + const PickPersonCoverPhotoWidget( + this.personEntity, { + super.key, + }); + + Future loadPersonFiles() async { + final result = await SearchService.instance + .getClusterFilesForPersonID(personEntity.remoteID); + + final List resultFiles = []; + for (final e in result.entries) { + resultFiles.addAll(e.value); + } + final List sortedFiles = List.from(resultFiles); + sortedFiles.sort((a, b) => b.creationTime!.compareTo(a.creationTime!)); + + return FileLoadResult(sortedFiles, false); + } + + @override + Widget build(BuildContext context) { + final ValueNotifier isFileSelected = ValueNotifier(false); + final selectedFiles = SelectedFiles(); + selectedFiles.addListener(() { + isFileSelected.value = selectedFiles.files.isNotEmpty; + }); + + return Padding( + padding: const EdgeInsets.all(0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: min(428, MediaQuery.of(context).size.width), + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 32, 0, 8), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Column( + children: [ + BottomOfTitleBarWidget( + title: const TitleBarTitleWidget( + title: "Select cover photo", + ), + caption: personEntity.data.name, + showCloseButton: true, + ), + Expanded( + child: GalleryFilesState( + child: Gallery( + asyncLoader: ( + creationStartTime, + creationEndTime, { + limit, + asc, + }) async { + final FileLoadResult result = + await loadPersonFiles(); + + return result; + }, + // reloadEvent: Bus.instance + // .on() + // .where( + // (event) => + // event.collectionID == collection.id, + // ), + tagPrefix: "pick_center_point_gallery", + selectedFiles: selectedFiles, + limitSelectionToOne: true, + showSelectAllByDefault: false, + ), + ), + ), + ], + ), + ), + SafeArea( + child: Container( + //inner stroke of 1pt + 15 pts of top padding = 16 pts + padding: const EdgeInsets.fromLTRB(16, 15, 16, 8), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: getEnteColorScheme(context).strokeFaint, + ), + ), + ), + child: Column( + children: [ + ValueListenableBuilder( + valueListenable: isFileSelected, + builder: (context, bool value, _) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + switchInCurve: Curves.easeInOutExpo, + switchOutCurve: Curves.easeInOutExpo, + child: ButtonWidget( + key: ValueKey(value), + isDisabled: !value, + buttonType: ButtonType.neutral, + labelText: S.of(context).useSelectedPhoto, + onTap: () async { + final selectedFile = + selectedFiles.files.first; + final result = await PersonService.instance + .updateAvatar( + personEntity, + selectedFile, + ); + Navigator.pop(context, result); + }, + ), + ); + }, + ), + const SizedBox(height: 8), + ButtonWidget( + buttonType: ButtonType.secondary, + buttonAction: ButtonAction.cancel, + labelText: S.of(context).cancel, + // labelText: collection.hasCover + // ? S.of(context).resetToDefault + // : S.of(context).cancel, + icon: null, + // icon: collection.hasCover + // ? Icons.restore_outlined + // : null, + onTap: () async { + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/people/add_person_action_sheet.dart b/mobile/lib/ui/viewer/people/add_person_action_sheet.dart index 9aa05b16e4..314d70e51d 100644 --- a/mobile/lib/ui/viewer/people/add_person_action_sheet.dart +++ b/mobile/lib/ui/viewer/people/add_person_action_sheet.dart @@ -1,357 +1,21 @@ import "dart:async"; -import "dart:developer"; -import "dart:math" as math; -import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; -import "package:logging/logging.dart"; -import 'package:modal_bottom_sheet/modal_bottom_sheet.dart'; -import "package:photos/core/event_bus.dart"; -import "package:photos/db/ml/db.dart"; -import "package:photos/events/people_changed_event.dart"; -import "package:photos/generated/l10n.dart"; import "package:photos/models/file/file.dart"; -import "package:photos/models/ml/face/person.dart"; -import 'package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart'; -import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; -import "package:photos/services/search_service.dart"; -import 'package:photos/theme/colors.dart'; -import 'package:photos/theme/ente_theme.dart'; -import 'package:photos/ui/common/loading_widget.dart'; -import 'package:photos/ui/components/bottom_of_title_bar_widget.dart'; -import 'package:photos/ui/components/buttons/button_widget.dart'; -import 'package:photos/ui/components/models/button_type.dart'; -import "package:photos/ui/components/text_input_widget.dart"; -import 'package:photos/ui/components/title_bar_title_widget.dart'; -import "package:photos/ui/viewer/people/new_person_item_widget.dart"; -import "package:photos/ui/viewer/people/person_row_item.dart"; -import "package:photos/utils/dialog_util.dart"; -import "package:photos/utils/toast_util.dart"; - -enum PersonActionType { - assignPerson, -} - -String _actionName( - BuildContext context, - PersonActionType type, -) { - String text = ""; - switch (type) { - case PersonActionType.assignPerson: - text = S.of(context).addNameOrMerge; - break; - } - return text; -} +import "package:photos/ui/viewer/people/save_or_edit_person.dart"; +import "package:photos/utils/navigation_util.dart"; Future showAssignPersonAction( BuildContext context, { required String clusterID, - PersonActionType actionType = PersonActionType.assignPerson, + EnteFile? file, bool showOptionToAddNewPerson = true, -}) { - return showBarModalBottomSheet( - context: context, - builder: (context) { - return PersonActionSheet( - actionType: actionType, - showOptionToCreateNewPerson: showOptionToAddNewPerson, - cluserID: clusterID, - ); - }, - shape: const RoundedRectangleBorder( - side: BorderSide(width: 0), - borderRadius: BorderRadius.vertical( - top: Radius.circular(5), - ), +}) async { + return routeToPage( + context, + SaveOrEditPerson( + clusterID, + file: file, ), - topControl: const SizedBox.shrink(), - backgroundColor: getEnteColorScheme(context).backgroundElevated, - barrierColor: backdropFaintDark, - enableDrag: false, ); } - -class PersonActionSheet extends StatefulWidget { - final PersonActionType actionType; - final String cluserID; - final bool showOptionToCreateNewPerson; - const PersonActionSheet({ - required this.actionType, - required this.cluserID, - required this.showOptionToCreateNewPerson, - super.key, - }); - - @override - State createState() => _PersonActionSheetState(); -} - -class _PersonActionSheetState extends State { - static const int cancelButtonSize = 80; - String _searchQuery = ""; - bool userAlreadyAssigned = false; - - @override - Widget build(BuildContext context) { - final bottomInset = MediaQuery.of(context).viewInsets.bottom; - final isKeyboardUp = bottomInset > 100; - return Padding( - padding: EdgeInsets.only( - bottom: isKeyboardUp ? bottomInset - cancelButtonSize : 0, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxWidth: math.min(428, MediaQuery.of(context).size.width), - ), - child: Padding( - padding: const EdgeInsets.fromLTRB(0, 32, 0, 8), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Column( - children: [ - BottomOfTitleBarWidget( - title: TitleBarTitleWidget( - title: _actionName(context, widget.actionType), - ), - // caption: 'Select or create a ', - ), - Padding( - padding: const EdgeInsets.only( - top: 16, - left: 16, - right: 16, - ), - child: TextInputWidget( - hintText: S.of(context).personName, - prefixIcon: Icons.search_rounded, - onChange: (value) { - setState(() { - _searchQuery = value; - }); - }, - isClearable: true, - shouldUnfocusOnClearOrSubmit: true, - borderRadius: 2, - ), - ), - _getPersonItems(), - ], - ), - ), - SafeArea( - child: Container( - //inner stroke of 1pt + 15 pts of top padding = 16 pts - padding: const EdgeInsets.fromLTRB(16, 15, 16, 8), - decoration: BoxDecoration( - border: Border( - top: BorderSide( - color: getEnteColorScheme(context).strokeFaint, - ), - ), - ), - child: ButtonWidget( - buttonType: ButtonType.secondary, - buttonAction: ButtonAction.cancel, - isInAlert: true, - labelText: S.of(context).cancel, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Flexible _getPersonItems() { - return Flexible( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 24, 4, 0), - child: FutureBuilder>( - future: _getPersonsWithRecentFile(), - builder: (context, snapshot) { - if (snapshot.hasError) { - log("Error: ${snapshot.error} ${snapshot.stackTrace}}"); - //Need to show an error on the UI here - if (kDebugMode) { - return Column( - children: [ - Text('${snapshot.error}'), - Text('${snapshot.stackTrace}'), - ], - ); - } else { - return const SizedBox.shrink(); - } - } else if (snapshot.hasData) { - final persons = snapshot.data!; - final searchResults = _searchQuery.isNotEmpty - ? persons - .where( - (element) => element.$1.data.name - .toLowerCase() - .contains(_searchQuery), - ) - .toList() - : persons; - // sort searchResults alphabetically by name - searchResults.sort( - (a, b) => a.$1.data.name.compareTo(b.$1.data.name), - ); - final shouldShowAddPerson = widget.showOptionToCreateNewPerson && - (_searchQuery.isEmpty || searchResults.isEmpty); - - return Scrollbar( - thumbVisibility: true, - radius: const Radius.circular(2), - child: Padding( - padding: const EdgeInsets.only(right: 12), - child: ListView.separated( - itemCount: searchResults.length + 1, - itemBuilder: (context, index) { - if (index == 0 && shouldShowAddPerson) { - return GestureDetector( - behavior: HitTestBehavior.opaque, - child: const NewPersonItemWidget(), - onTap: () async => { - addNewPerson( - context, - initValue: _searchQuery.trim(), - clusterID: widget.cluserID, - ), - }, - ); - } - if (index == 0 && !shouldShowAddPerson) { - return Padding( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 16), - child: Center( - child: Text( - S.of(context).mergeWithExisting, - style: getEnteTextTheme(context).body.copyWith( - color: - getEnteColorScheme(context).textMuted, - ), - ), - ), - ); - } - final person = searchResults[index - 1]; - return PersonRowItem( - person: person.$1, - personFile: person.$2, - onTap: () async { - if (userAlreadyAssigned) { - return; - } - userAlreadyAssigned = true; - await MLDataDB.instance.assignClusterToPerson( - personID: person.$1.remoteID, - clusterID: widget.cluserID, - ); - Bus.instance.fire(PeopleChangedEvent()); - - Navigator.pop(context, person); - }, - ); - }, - separatorBuilder: (context, index) { - return const SizedBox(height: 6); - }, - ), - ), - ); - } else { - return const EnteLoadingWidget(); - } - }, - ), - ), - ); - } - - Future addNewPerson( - BuildContext context, { - String initValue = '', - required String clusterID, - }) async { - PersonEntity? personEntity; - final result = await showTextInputDialog( - context, - title: S.of(context).newPerson, - submitButtonLabel: S.of(context).add, - hintText: S.of(context).addName, - alwaysShowSuccessState: false, - initialValue: initValue, - textCapitalization: TextCapitalization.words, - onSubmit: (String text) async { - if (userAlreadyAssigned) { - return; - } - // indicates user cancelled the rename request - if (text.trim() == "") { - return; - } - try { - userAlreadyAssigned = true; - personEntity = - await PersonService.instance.addPerson(text, clusterID); - final bool extraPhotosFound = - await ClusterFeedbackService.instance.checkAndDoAutomaticMerges( - personEntity!, - personClusterID: clusterID, - ); - if (extraPhotosFound) { - showShortToast(context, S.of(context).extraPhotosFound); - } - } catch (e, s) { - Logger("_PersonActionSheetState") - .severe("Failed to add person", e, s); - rethrow; - } - }, - ); - if (result is Exception) { - await showGenericErrorDialog(context: context, error: result); - } - if (personEntity != null) { - Bus.instance.fire(PeopleChangedEvent()); - Navigator.pop(context, personEntity); - } - } - - Future> _getPersonsWithRecentFile({ - bool excludeHidden = true, - }) async { - final persons = await PersonService.instance.getPersons(); - if (excludeHidden) { - persons.removeWhere((person) => person.data.isIgnored); - } - final List<(PersonEntity, EnteFile)> personAndFileID = []; - for (final person in persons) { - final clustersToFiles = - await SearchService.instance.getClusterFilesForPersonID( - person.remoteID, - ); - final files = clustersToFiles.values.expand((e) => e).toList(); - if (files.isEmpty) { - debugPrint( - "Person ${kDebugMode ? person.data.name : person.remoteID} has no files", - ); - continue; - } - personAndFileID.add((person, files.first)); - } - return personAndFileID; - } -} diff --git a/mobile/lib/ui/viewer/people/cluster_page.dart b/mobile/lib/ui/viewer/people/cluster_page.dart index cca4e2b6d2..625c3b40a3 100644 --- a/mobile/lib/ui/viewer/people/cluster_page.dart +++ b/mobile/lib/ui/viewer/people/cluster_page.dart @@ -141,31 +141,26 @@ class _ClusterPageState extends State { clusterID: widget.clusterID, ), actionIcon: Icons.add_outlined, - text: S.of(context).addAName, - subText: S.of(context).findPeopleByName, + text: S.of(context).savePerson, + subText: S.of(context).findThemQuickly, onTap: () async { if (widget.personID == null) { final result = await showAssignPersonAction( context, clusterID: widget.clusterID, + file: files.isEmpty ? null : files.first, ); - if (result != null && result is (PersonEntity, EnteFile)) { + if (result != null) { Navigator.pop(context); - // ignore: unawaited_futures - routeToPage( - context, - PeoplePage(person: result.$1, searchResult: null), - ); - } else if (result != null && result is PersonEntity) { - Navigator.pop(context); - // ignore: unawaited_futures + final person = + result is (PersonEntity, EnteFile) ? result.$1 : result; routeToPage( context, PeoplePage( - person: result, + person: person, searchResult: null, ), - ); + ).ignore(); } } else { showShortToast(context, "No personID or clusterID"); diff --git a/mobile/lib/ui/viewer/people/people_app_bar.dart b/mobile/lib/ui/viewer/people/people_app_bar.dart index 05da13c8b2..6a5e4b49de 100644 --- a/mobile/lib/ui/viewer/people/people_app_bar.dart +++ b/mobile/lib/ui/viewer/people/people_app_bar.dart @@ -22,6 +22,7 @@ import "package:photos/ui/viewer/hierarchicial_search/recommended_filters_for_ap import "package:photos/ui/viewer/people/add_person_action_sheet.dart"; import "package:photos/ui/viewer/people/people_page.dart"; import "package:photos/ui/viewer/people/person_cluster_suggestion.dart"; +import "package:photos/ui/viewer/people/save_or_edit_person.dart"; import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/navigation_util.dart"; @@ -62,6 +63,7 @@ class _AppBarWidgetState extends State { final GlobalKey shareButtonKey = GlobalKey(); bool isQuickLink = false; late GalleryType galleryType; + late PersonEntity person; @override void initState() { @@ -75,6 +77,7 @@ class _AppBarWidgetState extends State { Bus.instance.on().listen((event) { setState(() {}); }); + person = widget.person; _appBarTitle = widget.title; galleryType = widget.type; } @@ -143,37 +146,19 @@ class _AppBarWidgetState extends State { ); } - Future _renamePerson(BuildContext context) async { - final result = await showTextInputDialog( + Future _editPerson(BuildContext context) async { + final result = await routeToPage( context, - title: S.of(context).rename, - submitButtonLabel: S.of(context).done, - hintText: S.of(context).enterPersonName, - alwaysShowSuccessState: true, - initialValue: widget.person.data.name, - textCapitalization: TextCapitalization.words, - onSubmit: (String text) async { - // indicates user cancelled the rename request - if (text == "" || text == _appBarTitle!) { - return; - } - - try { - await PersonService.instance - .updateAttributes(widget.person.remoteID, name: text); - if (mounted) { - _appBarTitle = text; - setState(() {}); - } - Bus.instance.fire(PeopleChangedEvent()); - } catch (e, s) { - _logger.severe("Failed to rename album", e, s); - rethrow; - } - }, + SaveOrEditPerson( + person.data.assigned?.first.id ?? "", + person: person, + isEditing: true, + ), ); - if (result is Exception) { - await showGenericErrorDialog(context: context, error: result); + if (result is PersonEntity) { + _appBarTitle = result.data.name; + person = result; + setState(() {}); } } @@ -198,44 +183,31 @@ class _AppBarWidgetState extends State { const Padding( padding: EdgeInsets.all(8), ), - Text(S.of(context).rename), + Text(S.of(context).edit), + ], + ), + ), + PopupMenuItem( + value: PeoplePopupAction.reviewSuggestions, + child: Row( + children: [ + const Icon(Icons.search_outlined), + const Padding( + padding: EdgeInsets.all(8), + ), + Text(S.of(context).review), ], ), ), - // PopupMenuItem( - // value: PeoplPopupAction.setCover, - // child: Row( - // children: [ - // const Icon(Icons.image_outlined), - // const Padding( - // padding: EdgeInsets.all(8), - // ), - // Text(S.of(context).setCover), - // ], - // ), - // ), - PopupMenuItem( value: PeoplePopupAction.removeLabel, child: Row( children: [ - const Icon(Icons.remove_circle_outline), + const Icon(Icons.delete_outline), const Padding( padding: EdgeInsets.all(8), ), - Text(S.of(context).resetPerson), - ], - ), - ), - const PopupMenuItem( - value: PeoplePopupAction.reviewSuggestions, - child: Row( - children: [ - Icon(CupertinoIcons.square_stack_3d_down_right), - Padding( - padding: EdgeInsets.all(8), - ), - Text('Review suggestions'), + Text(S.of(context).remove), ], ), ), @@ -272,13 +244,12 @@ class _AppBarWidgetState extends State { unawaited( Navigator.of(context).push( MaterialPageRoute( - builder: (context) => - PersonReviewClusterSuggestion(widget.person), + builder: (context) => PersonReviewClusterSuggestion(person), ), ), ); } else if (value == PeoplePopupAction.rename) { - await _renamePerson(context); + await _editPerson(context); } else if (value == PeoplePopupAction.setCover) { await setCoverPhoto(context); } else if (value == PeoplePopupAction.unignore) { @@ -302,7 +273,7 @@ class _AppBarWidgetState extends State { firstButtonLabel: S.of(context).yesResetPerson, firstButtonOnTap: () async { try { - await PersonService.instance.deletePerson(widget.person.remoteID); + await PersonService.instance.deletePerson(person.remoteID); Navigator.of(context).pop(); } catch (e, s) { _logger.severe('Resetting person failed', e, s); @@ -336,21 +307,13 @@ class _AppBarWidgetState extends State { clusterID: widget.person.data.assigned!.first.id, ); Navigator.pop(context); - if (result != null && result is (PersonEntity, EnteFile)) { + if (result != null) { + final person = result is (PersonEntity, EnteFile) ? result.$1 : result; // ignore: unawaited_futures routeToPage( context, PeoplePage( - person: result.$1, - searchResult: null, - ), - ); - } else if (result != null && result is PersonEntity) { - // ignore: unawaited_futures - routeToPage( - context, - PeoplePage( - person: result, + person: person, searchResult: null, ), ); diff --git a/mobile/lib/ui/viewer/people/people_banner.dart b/mobile/lib/ui/viewer/people/people_banner.dart index 7a27990935..8380853739 100644 --- a/mobile/lib/ui/viewer/people/people_banner.dart +++ b/mobile/lib/ui/viewer/people/people_banner.dart @@ -54,14 +54,19 @@ class PeopleBanner extends StatelessWidget { case PeopleBannerType.addName: assert(faceWidget != null); backgroundColor = colorScheme.backgroundElevated; - startWidget = SizedBox( - width: 56, - height: 56, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular(4), + startWidget = Padding( + padding: const EdgeInsets.all(4.0), + child: SizedBox( + width: 56, + height: 56, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + ), + child: faceWidget!, ), - child: faceWidget!, ), ); roundedActionIcon = false; diff --git a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart index 54ff9975e8..a7af1a5867 100644 --- a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart +++ b/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart @@ -131,40 +131,94 @@ class _PersonClustersState extends State { setState(() {}); } }); - return GestureDetector( - onTap: () { - final List sortedFiles = - List.from(currentSuggestion.filesInCluster); - sortedFiles.sort( - (a, b) => b.creationTime!.compareTo(a.creationTime!), - ); - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ClusterPage( - sortedFiles, - personID: widget.person, - clusterID: clusterID, - showNamingBanner: false, + + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SingleChildScrollView( + child: GestureDetector( + onTap: () { + final List sortedFiles = List.from( + currentSuggestion.filesInCluster, + ); + sortedFiles.sort( + (a, b) => b.creationTime!.compareTo(a.creationTime!), + ); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ClusterPage( + sortedFiles, + personID: widget.person, + clusterID: clusterID, + showNamingBanner: false, + ), + ), + ); + }, + behavior: HitTestBehavior.opaque, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + vertical: 20, + ), + child: _buildSuggestionView( + clusterID, + distance, + usingMean, + files, + numberOfDifferentSuggestions, + allSuggestions, + generateFacedThumbnails, + ), + ), ), ), - ); - }, - behavior: HitTestBehavior.opaque, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 20, ), - child: _buildSuggestionView( - clusterID, - distance, - usingMean, - files, - numberOfDifferentSuggestions, - allSuggestions, - generateFacedThumbnails, + SafeArea( + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + right: 12.0, + bottom: 48, + ), + child: Row( + children: [ + Expanded( + child: ButtonWidget( + buttonType: ButtonType.tertiaryCritical, + icon: Icons.close, + labelText: context.l10n.no, + buttonSize: ButtonSize.large, + onTap: () async => { + await _handleUserClusterChoice( + clusterID, + false, + numberOfDifferentSuggestions, + ), + }, + ), + ), + const SizedBox(width: 12.0), + Expanded( + child: ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.yes, + buttonSize: ButtonSize.large, + onTap: () async => { + await _handleUserClusterChoice( + clusterID, + true, + numberOfDifferentSuggestions, + ), + }, + ), + ), + ], + ), + ), ), - ), + ], ); } else if (snapshot.hasError) { _logger.severe( @@ -199,8 +253,8 @@ class _PersonClustersState extends State { ); if (yesOrNo) { canGiveFeedback = false; - await MLDataDB.instance.assignClusterToPerson( - personID: widget.person.remoteID, + await ClusterFeedbackService.instance.addClusterToExistingPerson( + person: widget.person, clusterID: clusterID, ); Bus.instance.fire(PeopleChangedEvent()); @@ -283,62 +337,6 @@ class _PersonClustersState extends State { const SizedBox( height: 24.0, ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: Row( - children: [ - Expanded( - child: ButtonWidget( - buttonType: ButtonType.critical, - labelText: 'No', - buttonSize: ButtonSize.large, - onTap: () async => { - await _handleUserClusterChoice( - clusterID, - false, - numberOfSuggestions, - ), - }, - ), - ), - const SizedBox(width: 12.0), - Expanded( - child: ButtonWidget( - buttonType: ButtonType.primary, - labelText: context.l10n.yes, - buttonSize: ButtonSize.large, - onTap: () async => { - await _handleUserClusterChoice( - clusterID, - true, - numberOfSuggestions, - ), - }, - ), - ), - ], - ), - ), - // const SizedBox( - // height: 24.0, - // ), - // ButtonWidget( - // shouldSurfaceExecutionStates: false, - // buttonType: ButtonType.neutral, - // labelText: 'Assign different person', - // buttonSize: ButtonSize.small, - // onTap: () async { - // final result = await showAssignPersonAction( - // context, - // clusterID: clusterID, - // ); - // if (result != null && - // (result is (PersonEntity, EnteFile) || - // result is PersonEntity)) { - // await _rejectSuggestion(clusterID, numberOfSuggestions); - // } - // }, - // ), ], ); // Precompute face thumbnails for next suggestions, in case there are @@ -356,7 +354,7 @@ class _PersonClustersState extends State { )) { final files = suggestion.filesInCluster; final clusterID = suggestion.clusterIDToMerge; - for (final file in files.sublist(0, min(files.length, 8))) { + for (final file in files.sublist(0, min(files.length, 9))) { unawaited( PersonFaceWidget.precomputeNextFaceCrops( file, @@ -382,52 +380,54 @@ class _PersonClustersState extends State { String clusterID, Future> generateFaceThumbnails, ) { - return SizedBox( - height: MediaQuery.of(context).size.height * 0.4, - child: FutureBuilder>( - key: futureBuilderKeyFaceThumbnails, - future: generateFaceThumbnails, - builder: (context, snapshot) { - if (snapshot.hasData) { - final faceThumbnails = snapshot.data!; - canGiveFeedback = true; - return Column( - children: [ + return FutureBuilder>( + key: futureBuilderKeyFaceThumbnails, + future: generateFaceThumbnails, + builder: (context, snapshot) { + if (snapshot.hasData) { + final faceThumbnails = snapshot.data!; + canGiveFeedback = true; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildThumbnailWidgetsRow( + files, + clusterID, + faceThumbnails, + ), + ), + if (files.length > 3) const SizedBox(height: 12), + if (files.length > 3) Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildThumbnailWidgetsRow( files, clusterID, faceThumbnails, + start: 3, ), ), - if (files.length > 4) const SizedBox(height: 24), - if (files.length > 4) - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: _buildThumbnailWidgetsRow( - files, - clusterID, - faceThumbnails, - start: 4, - ), + if (files.length > 6) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _buildThumbnailWidgetsRow( + files, + clusterID, + faceThumbnails, + start: 6, ), - const SizedBox(height: 24.0), - Text( - "${files.length} photos", - style: getEnteTextTheme(context).body, ), - ], - ); - } else if (snapshot.hasError) { - // log the error - return const Center(child: Text("Error")); - } else { - canGiveFeedback = false; - return const Center(child: CircularProgressIndicator()); - } - }, - ), + ], + ); + } else if (snapshot.hasError) { + // log the error + return const Center(child: Text("Error")); + } else { + canGiveFeedback = false; + return const Center(child: CircularProgressIndicator()); + } + }, ); } @@ -438,20 +438,49 @@ class _PersonClustersState extends State { int start = 0, }) { return List.generate( - min(4, max(0, files.length - start)), + min(3, max(0, files.length - start)), (index) => Padding( padding: const EdgeInsets.all(8.0), child: SizedBox( - width: 72, - height: 72, - child: ClipOval( - child: PersonFaceWidget( - files[start + index], - clusterID: cluserId, - useFullFile: false, - thumbnailFallback: false, - faceCrop: faceThumbnails[files[start + index].uploadedFileID!], - ), + width: 100, + height: 100, + child: Stack( + children: [ + ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(75), + ), + ), + child: PersonFaceWidget( + files[start + index], + clusterID: cluserId, + useFullFile: false, + thumbnailFallback: false, + faceCrop: + faceThumbnails[files[start + index].uploadedFileID!], + ), + ), + if (start + index == 8 && files.length > 9) + ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(72), + ), + ), + child: Container( + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.7), + ), + child: Center( + child: Text( + '+${files.length - 8}', + style: darkTheme.textTheme.h3Bold, + ), + ), + ), + ), + ], ), ), ), @@ -495,10 +524,6 @@ class _PersonClustersState extends State { ); } - // futureClusterSuggestions = - // pastUserFeedback.map((element) => element.suggestion) - // as Future>; - fetch = false; futureClusterSuggestions = futureClusterSuggestions.then((list) { return list.sublist(currentSuggestionIndex) diff --git a/mobile/lib/ui/viewer/people/person_clusters_page.dart b/mobile/lib/ui/viewer/people/person_clusters_page.dart index 935ddbff87..68fd812407 100644 --- a/mobile/lib/ui/viewer/people/person_clusters_page.dart +++ b/mobile/lib/ui/viewer/people/person_clusters_page.dart @@ -1,8 +1,10 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; +import "package:intl/intl.dart"; import "package:logging/logging.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/events/people_changed_event.dart"; +import "package:photos/l10n/l10n.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/ml/face/person.dart"; import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; @@ -66,26 +68,23 @@ class _PersonClustersPageState extends State { child: Row( children: [ SizedBox( - width: 64, - height: 64, - child: files.isNotEmpty - ? ClipRRect( - borderRadius: const BorderRadius.all( - Radius.elliptical(16, 12), - ), - child: PersonFaceWidget( + width: 100, + height: 100, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(75), + ), + ), + child: files.isNotEmpty + ? PersonFaceWidget( files.first, clusterID: clusterID, - ), - ) - : const ClipRRect( - borderRadius: BorderRadius.all( - Radius.elliptical(16, 12), - ), - child: NoThumbnailWidget( + ) + : const NoThumbnailWidget( addBorder: false, ), - ), + ), ), const SizedBox( width: 8.0, @@ -150,3 +149,129 @@ class _PersonClustersPageState extends State { ); } } + +class PersonClustersWidget extends StatefulWidget { + final PersonEntity person; + final double? height; + + const PersonClustersWidget( + this.person, { + super.key, + this.height, + }); + + @override + State createState() => _PersonClustersWidgetState(); +} + +class _PersonClustersWidgetState extends State { + final Logger _logger = Logger("_PersonClustersWidgetState"); + + @override + Widget build(BuildContext context) { + return FutureBuilder>>( + future: SearchService.instance + .getClusterFilesForPersonID(widget.person.remoteID), + builder: (context, snapshot) { + if (snapshot.hasData) { + final clusters = snapshot.data!; + final List keys = clusters.keys.toList(); + // Sort the clusters by the number of files in each cluster, largest first + keys.sort( + (b, a) => clusters[a]!.length.compareTo(clusters[b]!.length), + ); + + return LayoutBuilder( + builder: (context, constraints) { + // Determine number of columns based on available width + // Minimum column width of 150, maximum of 250 + final double columnWidth = + MediaQuery.of(context).size.width > 600 ? 250 : 150; + final int crossAxisCount = + (constraints.maxWidth / columnWidth).floor().clamp(2, 5); + + // Calculate expected height based on number of rows + final int rowCount = (keys.length / crossAxisCount).ceil(); + // Thumbnail height + text height + spacing + final double expectedHeight = + widget.height ?? (rowCount * (110 + 30 + 8)); + + return SizedBox( + height: expectedHeight, + child: GridView.builder( + physics: + const NeverScrollableScrollPhysics(), // Disable scrolling + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: crossAxisCount, + childAspectRatio: + 1, // Adjust this to control height vs width + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemCount: keys.length, + itemBuilder: (context, index) { + final String clusterID = keys[index]; + final List files = clusters[clusterID]!; + + return GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ClusterPage( + files, + personID: widget.person, + clusterID: clusterID, + showNamingBanner: false, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 100, + height: 100, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(75), + ), + ), + child: files.isNotEmpty + ? PersonFaceWidget( + files.first, + clusterID: clusterID, + ) + : const NoThumbnailWidget( + addBorder: false, + ), + ), + ), + const SizedBox(height: 8), + Text( + context.l10n.memoryCount( + files.length, + NumberFormat().format(files.length), + ), + style: getEnteTextTheme(context).small, + textAlign: TextAlign.center, + ), + ], + ), + ); + }, + ), + ); + }, + ); + } else if (snapshot.hasError) { + _logger.warning("Failed to get cluster", snapshot.error); + return const Center(child: Text("Error")); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + ); + } +} diff --git a/mobile/lib/ui/viewer/people/person_row_item.dart b/mobile/lib/ui/viewer/people/person_row_item.dart index 95e2453eaa..10716f4196 100644 --- a/mobile/lib/ui/viewer/people/person_row_item.dart +++ b/mobile/lib/ui/viewer/people/person_row_item.dart @@ -9,11 +9,11 @@ class PersonRowItem extends StatelessWidget { final VoidCallback onTap; const PersonRowItem({ - Key? key, + super.key, required this.person, required this.personFile, required this.onTap, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -36,3 +36,49 @@ class PersonRowItem extends StatelessWidget { ); } } + +class PersonGridItem extends StatelessWidget { + final PersonEntity person; + final EnteFile personFile; + final VoidCallback onTap; + + const PersonGridItem({ + super.key, + required this.person, + required this.personFile, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 112, + height: 112, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(80), + ), + ), + child: PersonFaceWidget(personFile, personId: person.remoteID), + ), + ), + const SizedBox(height: 4), + Text( + person.data.name, + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/people/save_or_edit_person.dart b/mobile/lib/ui/viewer/people/save_or_edit_person.dart new file mode 100644 index 0000000000..6545ec94ef --- /dev/null +++ b/mobile/lib/ui/viewer/people/save_or_edit_person.dart @@ -0,0 +1,476 @@ +import 'dart:async'; +import "dart:developer"; + +import "package:flutter/foundation.dart"; +import "package:flutter/material.dart"; +import "package:logging/logging.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/db/ml/db.dart"; +import "package:photos/ente_theme_data.dart"; +import "package:photos/events/people_changed_event.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/ml/face/person.dart"; +import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart"; +import "package:photos/services/machine_learning/face_ml/person/person_service.dart"; +import "package:photos/services/search_service.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/common/date_input.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; +import "package:photos/ui/viewer/file/no_thumbnail_widget.dart"; +import "package:photos/ui/viewer/gallery/hooks/pick_person_avatar.dart"; +import "package:photos/ui/viewer/people/person_clusters_page.dart"; +import "package:photos/ui/viewer/people/person_row_item.dart"; +import "package:photos/ui/viewer/search/result/person_face_widget.dart"; +import "package:photos/utils/dialog_util.dart"; +import "package:photos/utils/toast_util.dart"; + +class SaveOrEditPerson extends StatefulWidget { + final String? clusterID; + final EnteFile? file; + final bool isEditing; + final PersonEntity? person; + + const SaveOrEditPerson( + this.clusterID, { + super.key, + this.file, + this.person, + this.isEditing = false, + }) : assert( + !isEditing || person != null, 'Person cannot be null when editing',); + + @override + State createState() => _SaveOrEditPersonState(); +} + +class _SaveOrEditPersonState extends State { + bool isKeypadOpen = false; + String _inputName = ""; + String? _selectedDate; + bool userAlreadyAssigned = false; + late final Logger _logger = Logger("_SavePersonState"); + Timer? _debounce; + List<(PersonEntity, EnteFile)> _cachedPersons = []; + + @override + void initState() { + super.initState(); + _inputName = widget.person?.data.name ?? ""; + _selectedDate = widget.person?.data.birthDate; + } + + @override + void dispose() { + _debounce?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: isKeypadOpen, + appBar: AppBar( + title: Align( + alignment: Alignment.centerLeft, + child: Text( + widget.isEditing + ? context.l10n.editPerson + : context.l10n.savePerson, + ), + ), + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + padding: const EdgeInsets.only( + bottom: 32.0, + left: 16.0, + right: 16.0, + ), + child: Column( + children: [ + const SizedBox(height: 48), + if (widget.person != null) + FutureBuilder<(String, EnteFile)>( + future: _getRecentFileWithClusterID(widget.person!), + builder: (context, snapshot) { + if (snapshot.hasData) { + final String personClusterID = snapshot.data!.$1; + final personFile = snapshot.data!.$2; + return Stack( + children: [ + SizedBox( + height: 110, + width: 110, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(80), + ), + ), + child: snapshot.hasData + ? PersonFaceWidget( + personFile, + clusterID: personClusterID, + personId: widget.person!.remoteID, + ) + : const NoThumbnailWidget( + addBorder: false, + ), + ), + ), + if (widget.person != null) + Positioned( + right: 0, + bottom: 0, + child: Container( + width: 28, + height: 28, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(8.0), + boxShadow: Theme.of(context) + .colorScheme + .enteTheme + .shadowMenu, + color: getEnteColorScheme(context) + .backgroundElevated2, + ), + child: IconButton( + icon: const Icon(Icons.edit), + iconSize: + 16, // specify the size of the icon + onPressed: () async { + await showPersonAvatarPhotoSheet( + context, + widget.person!, + ); + // if (person != null) { + Navigator.pop(context); + }, + ), + ), + ), + ], + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + if (widget.person == null) + SizedBox( + height: 110, + width: 110, + child: ClipPath( + clipper: ShapeBorderClipper( + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(80), + ), + ), + child: widget.file != null + ? PersonFaceWidget( + widget.file!, + clusterID: widget.clusterID, + ) + : const NoThumbnailWidget( + addBorder: false, + ), + ), + ), + const SizedBox(height: 36), + TextFormField( + onChanged: (value) { + if (_debounce?.isActive ?? false) _debounce?.cancel(); + _debounce = + Timer(const Duration(milliseconds: 300), () { + setState(() { + _inputName = value; + }); + }); + }, + initialValue: _inputName, + decoration: InputDecoration( + focusedBorder: OutlineInputBorder( + borderRadius: + const BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide( + color: getEnteColorScheme(context).strokeMuted, + ), + ), + fillColor: getEnteColorScheme(context).fillFaint, + filled: true, + hintText: context.l10n.enterName, + hintStyle: getEnteTextTheme(context).bodyFaint, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + border: UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(8), + ), + ), + ), + const SizedBox(height: 16), + DatePickerField( + hintText: context.l10n.enterDateOfBirth, + firstDate: DateTime(100), + lastDate: DateTime.now(), + initialValue: _selectedDate, + isRequired: false, + onChanged: (date) { + setState(() { + // format date to yyyy-MM-dd + _selectedDate = + date?.toIso8601String().split("T").first; + }); + }, + ), + const SizedBox(height: 32), + ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.save, + isDisabled: !changed, + onTap: () async { + if (widget.isEditing) { + await updatePerson(context); + } else { + await addNewPerson( + context, + text: _inputName, + clusterID: widget.clusterID!, + ); + } + }, + ), + const SizedBox(height: 32), + if (!widget.isEditing) _getPersonItems(), + if (widget.isEditing) + Align( + alignment: Alignment.centerLeft, + child: Text( + context.l10n.mergedPhotos, + style: getEnteTextTheme(context).body, + ), + ), + if (widget.isEditing) + Padding( + padding: const EdgeInsets.only(bottom: 12.0, top: 24.0), + child: PersonClustersWidget(widget.person!), + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget _getPersonItems() { + return Padding( + padding: const EdgeInsets.fromLTRB(0, 12, 4, 0), + child: StreamBuilder>( + stream: _getPersonsWithRecentFileStream(), + builder: (context, snapshot) { + if (snapshot.hasError) { + log("Error: ${snapshot.error} ${snapshot.stackTrace}}"); + if (kDebugMode) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('${snapshot.error}'), + Text('${snapshot.stackTrace}'), + ], + ); + } else { + return const SizedBox.shrink(); + } + } else if (snapshot.hasData) { + final persons = snapshot.data!; + final searchResults = _inputName.isNotEmpty + ? persons + .where( + (element) => element.$1.data.name + .toLowerCase() + .contains(_inputName.toLowerCase()), + ) + .toList() + : persons; + searchResults.sort( + (a, b) => a.$1.data.name.compareTo(b.$1.data.name), + ); + if (searchResults.isEmpty) { + return const SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // left align + Padding( + padding: const EdgeInsets.only(top: 12, bottom: 12), + child: Text( + context.l10n.orMergeWithExistingPerson, + style: getEnteTextTheme(context).largeBold, + ), + ), + + SizedBox( + height: 160, // Adjust this height based on your needs + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith( + scrollbars: true, + ), + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: const EdgeInsets.only(right: 8), + itemCount: searchResults.length, + itemBuilder: (context, index) { + final person = searchResults[index]; + return PersonGridItem( + person: person.$1, + personFile: person.$2, + onTap: () async { + if (userAlreadyAssigned) { + return; + } + userAlreadyAssigned = true; + await MLDataDB.instance.assignClusterToPerson( + personID: person.$1.remoteID, + clusterID: widget.clusterID!, + ); + Bus.instance.fire(PeopleChangedEvent()); + + Navigator.pop(context, person); + }, + ); + }, + separatorBuilder: (context, index) { + return const SizedBox(width: 6); + }, + ), + ), + ), + ], + ); + } else { + return const EnteLoadingWidget(); + } + }, + ), + ); + } + + Stream> + _getPersonsWithRecentFileStream() async* { + if (_cachedPersons.isEmpty) { + _cachedPersons = await _getPersonsWithRecentFile(); + } + yield _cachedPersons; + } + + Future addNewPerson( + BuildContext context, { + String text = '', + required String clusterID, + }) async { + try { + if (userAlreadyAssigned) { + return; + } + if (text.trim() == "") { + return; + } + userAlreadyAssigned = true; + final personEntity = + await PersonService.instance.addPerson(text, clusterID); + final bool extraPhotosFound = + await ClusterFeedbackService.instance.checkAndDoAutomaticMerges( + personEntity, + personClusterID: clusterID, + ); + if (extraPhotosFound) { + showShortToast(context, S.of(context).extraPhotosFound); + } + Bus.instance.fire(PeopleChangedEvent()); + Navigator.pop(context, personEntity); + } catch (e) { + _logger.severe("Error adding new person", e); + userAlreadyAssigned = false; + await showGenericErrorDialog(context: context, error: e); + } + } + + bool get changed => widget.isEditing + ? (_inputName.trim() != widget.person!.data.name || + _selectedDate != widget.person!.data.birthDate) + : _inputName.trim().isNotEmpty; + + Future updatePerson(BuildContext context) async { + try { + final String name = _inputName.trim(); + final String? birthDate = _selectedDate; + final personEntity = await PersonService.instance.updateAttributes( + widget.person!.remoteID, + name: name, + birthDate: birthDate, + ); + + Bus.instance.fire(PeopleChangedEvent()); + Navigator.pop(context, personEntity); + } catch (e) { + _logger.severe("Error adding updating person", e); + await showGenericErrorDialog(context: context, error: e); + } + } + + Future> _getPersonsWithRecentFile({ + bool excludeHidden = true, + }) async { + final persons = await PersonService.instance.getPersons(); + if (excludeHidden) { + persons.removeWhere((person) => person.data.isIgnored); + } + final List<(PersonEntity, EnteFile)> personAndFileID = []; + for (final person in persons) { + final clustersToFiles = + await SearchService.instance.getClusterFilesForPersonID( + person.remoteID, + ); + final files = clustersToFiles.values.expand((e) => e).toList(); + if (files.isEmpty) { + debugPrint( + "Person ${kDebugMode ? person.data.name : person.remoteID} has no files", + ); + continue; + } + personAndFileID.add((person, files.first)); + } + return personAndFileID; + } + + Future<(String, EnteFile)> _getRecentFileWithClusterID( + PersonEntity person,) async { + final clustersToFiles = + await SearchService.instance.getClusterFilesForPersonID( + person.remoteID, + ); + final files = clustersToFiles.values.expand((e) => e).toList(); + if (files.isEmpty) { + debugPrint( + "Person ${kDebugMode ? person.data.name : person.remoteID} has no files", + ); + return ("", EnteFile()); + } + return (person.remoteID, files.first); + } +} diff --git a/mobile/lib/utils/delete_file_util.dart b/mobile/lib/utils/delete_file_util.dart index d16ac4b1b6..57ecd9ac6e 100644 --- a/mobile/lib/utils/delete_file_util.dart +++ b/mobile/lib/utils/delete_file_util.dart @@ -468,7 +468,7 @@ Future> deleteLocalFilesInBatches( } } } - Navigator.of(dialogKey.currentContext!, rootNavigator: true).pop('dialog'); + Navigator.of(dialogKey.currentContext!).pop('dialog'); return deletedIDs; } diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 99804b5cd6..ab5408cf3e 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.9.58+958 +version: 0.9.59+959 publish_to: none environment: diff --git a/server/pkg/repo/storagebonus/bf_addon.go b/server/pkg/repo/storagebonus/bf_addon.go index 5e012f01fc..2c5a759c28 100644 --- a/server/pkg/repo/storagebonus/bf_addon.go +++ b/server/pkg/repo/storagebonus/bf_addon.go @@ -3,6 +3,8 @@ package storagebonus import ( "context" "fmt" + + "github.com/ente-io/museum/ente" "github.com/ente-io/museum/ente/storagebonus" ) @@ -22,12 +24,34 @@ func (r *Repository) RemoveAddOnBonus(ctx context.Context, bonusType storagebonu if err := _validate(bonusType); err != nil { return 0, err } + bonusID := fmt.Sprintf("%s-%d", bonusType, userID) - res, err := r.DB.ExecContext(ctx, "DELETE FROM storage_bonus WHERE bonus_id = $1", bonusID) + + tx, err := r.DB.BeginTx(ctx, nil) if err != nil { - return 0, err + return 0, fmt.Errorf("failed to begin transaction: %w", err) } - return res.RowsAffected() + defer tx.Rollback() // Will be no-op if transaction is committed + res, err := tx.ExecContext(ctx, "DELETE FROM storage_bonus WHERE bonus_id = $1", bonusID) + if err != nil { + return 0, fmt.Errorf("failed to execute delete query: %w", err) + } + rowsAffected, err := res.RowsAffected() + if err != nil { + return 0, fmt.Errorf("failed to get affected rows: %w", err) + } + switch { + case rowsAffected == 0: + return 0, ente.NewBadRequestWithMessage(fmt.Sprintf("bonus not found for user %d with bonusID %s", userID, bonusID)) + case rowsAffected > 1: + return 0, fmt.Errorf("more than one (%d) bonus found for user %d with bonusID %s", rowsAffected, userID, bonusID) + } + + if err := tx.Commit(); err != nil { + return 0, fmt.Errorf("failed to commit transaction: %w", err) + } + + return 1, nil // We know exactly one row was affected at this point } func (r *Repository) UpdateAddOnBonus(ctx context.Context, bonusType storagebonus.BonusType, userID int64, validTill int64, storage int64) error { diff --git a/web/apps/accounts/.eslintrc.js b/web/apps/accounts/.eslintrc.js deleted file mode 100644 index b1aff972c9..0000000000 --- a/web/apps/accounts/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - extends: ["@/build-config/eslintrc-next"], - ignorePatterns: ["next.config.js", "next-env.d.ts"], -}; diff --git a/web/apps/accounts/eslint.config.mjs b/web/apps/accounts/eslint.config.mjs new file mode 100644 index 0000000000..06dd5eefb9 --- /dev/null +++ b/web/apps/accounts/eslint.config.mjs @@ -0,0 +1 @@ +export { default } from "@/build-config/eslintrc-next-app.mjs"; diff --git a/web/apps/accounts/src/pages/passkeys/index.tsx b/web/apps/accounts/src/pages/passkeys/index.tsx index ac741f06d7..5e5b78dfff 100644 --- a/web/apps/accounts/src/pages/passkeys/index.tsx +++ b/web/apps/accounts/src/pages/passkeys/index.tsx @@ -4,7 +4,6 @@ import { SidebarDrawer } from "@/base/components/mui/SidebarDrawer"; import { Titlebar } from "@/base/components/Titlebar"; import { errorDialogAttributes } from "@/base/components/utils/dialog"; import log from "@/base/log"; -import { ensure } from "@/utils/ensure"; import { CenteredFlex } from "@ente/shared/components/Container"; import { EnteMenuItem } from "@ente/shared/components/Menu/EnteMenuItem"; import SingleInputForm from "@ente/shared/components/SingleInputForm"; @@ -64,7 +63,7 @@ const Page: React.FC = () => { const refreshPasskeys = useCallback(async () => { try { - setPasskeys(await getPasskeys(ensure(token))); + setPasskeys(await getPasskeys(token!)); } catch (e) { log.error("Failed to fetch passkeys", e); showPasskeyFetchFailedErrorDialog(); @@ -103,7 +102,7 @@ const Page: React.FC = () => { resetForm: () => void, ) => { try { - await registerPasskey(ensure(token), inputValue); + await registerPasskey(token!, inputValue); } catch (e) { log.error("Failed to register a new passkey", e); // If the user cancels the operation, then an error with name @@ -273,7 +272,7 @@ const ManagePasskeyDrawer: React.FC = ({ text: t("delete"), color: "critical", action: async () => { - await deletePasskey(ensure(token), ensure(passkey).id); + await deletePasskey(token!, passkey!.id); onUpdateOrDeletePasskey(); }, }, diff --git a/web/apps/accounts/src/pages/passkeys/verify.tsx b/web/apps/accounts/src/pages/passkeys/verify.tsx index 196682be28..06e0cdbc8d 100644 --- a/web/apps/accounts/src/pages/passkeys/verify.tsx +++ b/web/apps/accounts/src/pages/passkeys/verify.tsx @@ -2,7 +2,6 @@ import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; import log from "@/base/log"; import type { TwoFactorAuthorizationResponse } from "@/base/types/credentials"; -import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { VerticallyCentered } from "@ente/shared/components/Container"; import InfoIcon from "@mui/icons-material/Info"; @@ -212,7 +211,7 @@ const Page = () => { if (successRedirectURL) redirectToURL(successRedirectURL); }, [successRedirectURL]); - const handleVerify = () => void authenticateContinue(ensure(continuation)); + const handleVerify = () => void authenticateContinue(continuation!); const handleRetry = () => void authenticate(); @@ -232,7 +231,7 @@ const Page = () => { return () => redirectToPasskeyRecoverPage(new URL(recover)); })(); - const handleRedirectAgain = () => redirectToURL(ensure(successRedirectURL)); + const handleRedirectAgain = () => redirectToURL(successRedirectURL!); const components: Record = { loading: , diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 3c12e31d79..632cd590cc 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -8,7 +8,6 @@ import { isDevBuild } from "@/base/env"; import { clientPackageHeader, ensureOk, HTTPError } from "@/base/http"; import { apiURL } from "@/base/origins"; import { TwoFactorAuthorizationResponse } from "@/base/types/credentials"; -import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; @@ -116,7 +115,7 @@ export const registerPasskey = async (token: string, name: string) => { const { sessionID, options } = await beginPasskeyRegistration(token); // Ask the browser to new (public key) credentials using these options. - const credential = ensure(await navigator.credentials.create(options)); + const credential = (await navigator.credentials.create(options))!; // Finish by letting the backend know about these credentials so that it can // save the public key for future authentication. diff --git a/web/apps/accounts/src/types/context.ts b/web/apps/accounts/src/types/context.ts index dece58358d..5218ae033f 100644 --- a/web/apps/accounts/src/types/context.ts +++ b/web/apps/accounts/src/types/context.ts @@ -1,5 +1,4 @@ import type { AccountsContextT } from "@/accounts/types/context"; -import { ensure } from "@/utils/ensure"; import { createContext, useContext } from "react"; /** @@ -13,7 +12,7 @@ type AppContextT = Omit; export const AppContext = createContext(undefined); /** - * Utility hook to get the {@link AppContextT}, throwing an exception if it is - * not defined. + * Utility hook to get the {@link AppContextT} expected to be available to all + * React components in the Accounts app's React tree. */ -export const useAppContext = (): AppContextT => ensure(useContext(AppContext)); +export const useAppContext = (): AppContextT => useContext(AppContext)!; diff --git a/web/apps/auth/.eslintrc.js b/web/apps/auth/.eslintrc.js deleted file mode 100644 index a407643d8a..0000000000 --- a/web/apps/auth/.eslintrc.js +++ /dev/null @@ -1,13 +0,0 @@ -module.exports = { - // When root is set to true, ESLint will stop looking for configuration files in parent directories. - // This is required here to ensure desktop picks the right eslint config, where this app is - // packaged as a submodule. - root: true, - extends: ["@ente/eslint-config"], - parser: "@typescript-eslint/parser", - parserOptions: { - tsconfigRootDir: __dirname, - project: "./tsconfig.json", - }, - ignorePatterns: [".eslintrc.js", "next.config.js", "out"], -}; diff --git a/web/apps/auth/eslint.config.mjs b/web/apps/auth/eslint.config.mjs new file mode 100644 index 0000000000..d9bbd0401c --- /dev/null +++ b/web/apps/auth/eslint.config.mjs @@ -0,0 +1,22 @@ +import config from "@/build-config/eslintrc-next-app.mjs"; + +export default [ + ...config, + { + rules: { + /* TODO: + * "This rule requires the `strictNullChecks` compiler option to be + * turned on to function correctly" + */ + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-argument": "off", + /** TODO: Disabled as we migrate, try to prune these again */ + "react-hooks/exhaustive-deps": "off", + }, + }, +]; diff --git a/web/apps/auth/package.json b/web/apps/auth/package.json index f43932ccdb..7ec5749d3c 100644 --- a/web/apps/auth/package.json +++ b/web/apps/auth/package.json @@ -5,10 +5,11 @@ "dependencies": { "@/accounts": "*", "@/base": "*", - "@/build-config": "*", - "@ente/eslint-config": "*", "@ente/shared": "*", "jssha": "~3.3.1", "otpauth": "9.2.4" + }, + "devDependencies": { + "@/build-config": "*" } } diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index 41e61d200c..98feccc295 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -1,5 +1,4 @@ import { accountLogout } from "@/accounts/services/logout"; -import type { AccountsContextT } from "@/accounts/types/context"; import { clientPackageName, staticAppTitle } from "@/base/app"; import { CustomHead } from "@/base/components/Head"; import { AttributedMiniDialog } from "@/base/components/MiniDialog"; @@ -12,7 +11,6 @@ import { logStartupBanner, logUnhandledErrorsAndRejections, } from "@/base/log-web"; -import { ensure } from "@/utils/ensure"; import { MessageContainer } from "@ente/shared/components/MessageContainer"; import { useLocalState } from "@ente/shared/hooks/useLocalState"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -29,30 +27,11 @@ import { ThemeProvider } from "@mui/material/styles"; import { t } from "i18next"; import type { AppProps } from "next/app"; import { useRouter } from "next/router"; -import React, { - createContext, - useCallback, - useContext, - useEffect, - useState, -} from "react"; +import React, { useCallback, useEffect, useState } from "react"; +import { AppContext } from "types/context"; import "../../public/css/global.css"; -/** - * Properties available via {@link AppContext} to the Auth app's React tree. - */ -type AppContextT = AccountsContextT & { - themeColor: THEME_COLOR; - setThemeColor: (themeColor: THEME_COLOR) => void; -}; - -/** The React {@link Context} available to all pages. */ -export const AppContext = createContext(undefined); - -/** Utility hook to reduce amount of boilerplate in account related pages. */ -export const useAppContext = () => ensure(useContext(AppContext)); - const App: React.FC = ({ Component, pageProps }) => { const router = useRouter(); const [isI18nReady, setIsI18nReady] = useState(false); @@ -71,7 +50,7 @@ const App: React.FC = ({ Component, pageProps }) => { useEffect(() => { void setupI18n().finally(() => setIsI18nReady(true)); const user = getData(LS_KEYS.USER) as User | undefined | null; - migrateKVToken(user); + void migrateKVToken(user); logStartupBanner(user?.id); HTTPService.setHeaders({ "X-Client-Package": clientPackageName }); logUnhandledErrorsAndRejections(true); diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index 6fb996cf3f..2fd032d5c2 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -1,9 +1,8 @@ -import { sessionExpiredDialogAttributes } from "@/accounts/components/LoginComponents"; +import { sessionExpiredDialogAttributes } from "@/accounts/components/utils/dialog"; import { stashRedirect } from "@/accounts/services/redirect"; import { EnteLogo } from "@/base/components/EnteLogo"; import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import { NavbarBase } from "@/base/components/Navbar"; -import { ensure } from "@/utils/ensure"; import { HorizontalFlex, VerticallyCentered, @@ -17,15 +16,14 @@ import MoreHoriz from "@mui/icons-material/MoreHoriz"; import { Button, ButtonBase, Snackbar, TextField, styled } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; -import React, { useContext, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { generateOTPs, type Code } from "services/code"; import { getAuthCodes } from "services/remote"; -import { AppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page: React.FC = () => { - const { logout, showNavBar, showMiniDialog } = ensure( - useContext(AppContext), - ); + const { logout, showNavBar, showMiniDialog } = useAppContext(); + const router = useRouter(); const [codes, setCodes] = useState([]); const [hasFetched, setHasFetched] = useState(false); @@ -44,7 +42,7 @@ const Page: React.FC = () => { e.message == CustomError.KEY_MISSING ) { stashRedirect(PAGES.AUTH); - router.push("/"); + void router.push("/"); } else if (e instanceof ApiError && e.httpStatusCode == 401) { // We get back a 401 Unauthorized if the token is not valid. showSessionExpiredDialog(); @@ -141,7 +139,7 @@ const Page: React.FC = () => { export default Page; const AuthNavbar: React.FC = () => { - const { logout } = ensure(useContext(AppContext)); + const { logout } = useAppContext(); return ( @@ -186,11 +184,11 @@ const CodeDisplay: React.FC = ({ code }) => { } }; - const copyCode = () => { - navigator.clipboard.writeText(otp); - setHasCopied(true); - setTimeout(() => setHasCopied(false), 2000); - }; + const copyCode = () => + void navigator.clipboard.writeText(otp).then(() => { + setHasCopied(true); + setTimeout(() => setHasCopied(false), 2000); + }); useEffect(() => { // Generate to set the initial otp and nextOTP on component mount. diff --git a/web/apps/auth/src/pages/change-email.tsx b/web/apps/auth/src/pages/change-email.tsx index cef4716203..74b094aeed 100644 --- a/web/apps/auth/src/pages/change-email.tsx +++ b/web/apps/auth/src/pages/change-email.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/change-email"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/change-password.tsx b/web/apps/auth/src/pages/change-password.tsx index 2232edc6bc..3ce2c6267d 100644 --- a/web/apps/auth/src/pages/change-password.tsx +++ b/web/apps/auth/src/pages/change-password.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/change-password"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/credentials.tsx b/web/apps/auth/src/pages/credentials.tsx index fa3cc8fad6..17dd9fef70 100644 --- a/web/apps/auth/src/pages/credentials.tsx +++ b/web/apps/auth/src/pages/credentials.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/credentials"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/generate.tsx b/web/apps/auth/src/pages/generate.tsx index a82d0a46f3..12723d0b0a 100644 --- a/web/apps/auth/src/pages/generate.tsx +++ b/web/apps/auth/src/pages/generate.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/generate"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/index.tsx b/web/apps/auth/src/pages/index.tsx index e5540896e3..81dd02b769 100644 --- a/web/apps/auth/src/pages/index.tsx +++ b/web/apps/auth/src/pages/index.tsx @@ -4,10 +4,7 @@ import React, { useEffect } from "react"; const Page: React.FC = () => { const router = useRouter(); - useEffect(() => { - router.push(PAGES.LOGIN); - }, []); - + useEffect(() => void router.push(PAGES.LOGIN), []); return <>; }; diff --git a/web/apps/auth/src/pages/login.tsx b/web/apps/auth/src/pages/login.tsx index a61718a309..c373229f00 100644 --- a/web/apps/auth/src/pages/login.tsx +++ b/web/apps/auth/src/pages/login.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/login"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/passkeys/finish.tsx b/web/apps/auth/src/pages/passkeys/finish.tsx index e75df9677b..e7c08cae2c 100644 --- a/web/apps/auth/src/pages/passkeys/finish.tsx +++ b/web/apps/auth/src/pages/passkeys/finish.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/passkeys/finish"; -import { useAppContext } from "../_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/passkeys/recover.tsx b/web/apps/auth/src/pages/passkeys/recover.tsx index d7ac1c30df..a156314ed5 100644 --- a/web/apps/auth/src/pages/passkeys/recover.tsx +++ b/web/apps/auth/src/pages/passkeys/recover.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/two-factor/recover"; -import { useAppContext } from "../_app"; +import { useAppContext } from "types/context"; const Page = () => ( diff --git a/web/apps/auth/src/pages/recover.tsx b/web/apps/auth/src/pages/recover.tsx index 3fb3866a8a..62ceb6a63a 100644 --- a/web/apps/auth/src/pages/recover.tsx +++ b/web/apps/auth/src/pages/recover.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/recover"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/share.tsx b/web/apps/auth/src/pages/share.tsx index 97d32e415d..adb7cf37e0 100644 --- a/web/apps/auth/src/pages/share.tsx +++ b/web/apps/auth/src/pages/share.tsx @@ -89,7 +89,7 @@ const Share: React.FC = () => { ); } }; - decryptCode(); + void decryptCode(); }, []); useEffect(() => { diff --git a/web/apps/auth/src/pages/signup.tsx b/web/apps/auth/src/pages/signup.tsx index 37386b9b4e..e1bb54b598 100644 --- a/web/apps/auth/src/pages/signup.tsx +++ b/web/apps/auth/src/pages/signup.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/signup"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/two-factor/recover.tsx b/web/apps/auth/src/pages/two-factor/recover.tsx index 1c36e691ec..dbf10270fb 100644 --- a/web/apps/auth/src/pages/two-factor/recover.tsx +++ b/web/apps/auth/src/pages/two-factor/recover.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/two-factor/recover"; -import { useAppContext } from "../_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/two-factor/setup.tsx b/web/apps/auth/src/pages/two-factor/setup.tsx index 404fbb271a..7e6691e98e 100644 --- a/web/apps/auth/src/pages/two-factor/setup.tsx +++ b/web/apps/auth/src/pages/two-factor/setup.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/two-factor/setup"; -import { useAppContext } from "../_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/two-factor/verify.tsx b/web/apps/auth/src/pages/two-factor/verify.tsx index b6fc679f8c..0a449ad470 100644 --- a/web/apps/auth/src/pages/two-factor/verify.tsx +++ b/web/apps/auth/src/pages/two-factor/verify.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/two-factor/verify"; -import { useAppContext } from "../_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/pages/verify.tsx b/web/apps/auth/src/pages/verify.tsx index 64aeb883a9..572f40aa99 100644 --- a/web/apps/auth/src/pages/verify.tsx +++ b/web/apps/auth/src/pages/verify.tsx @@ -1,5 +1,5 @@ import Page_ from "@/accounts/pages/verify"; -import { useAppContext } from "./_app"; +import { useAppContext } from "types/context"; const Page = () => ; diff --git a/web/apps/auth/src/services/code.ts b/web/apps/auth/src/services/code.ts index c604bae0ce..a7b1662b5d 100644 --- a/web/apps/auth/src/services/code.ts +++ b/web/apps/auth/src/services/code.ts @@ -1,4 +1,3 @@ -import { ensure } from "@/utils/ensure"; import { HOTP, TOTP } from "otpauth"; import { Steam } from "./steam"; @@ -168,8 +167,8 @@ const parseIssuer = (url: URL, path: string): string => { let p = decodeURIComponent(path); if (p.startsWith("/")) p = p.slice(1); - if (p.includes(":")) p = ensure(p.split(":")[0]); - else if (p.includes("-")) p = ensure(p.split("-")[0]); + if (p.includes(":")) p = p.split(":")[0]!; + else if (p.includes("-")) p = p.split("-")[0]!; return p; }; @@ -206,7 +205,7 @@ const parseCounter = (url: URL): number | undefined => { }; const parseSecret = (url: URL): string => - ensure(url.searchParams.get("secret")).replaceAll(" ", "").toUpperCase(); + url.searchParams.get("secret")!.replaceAll(" ", "").toUpperCase(); /** * Generate a pair of OTPs (one time passwords) from the given {@link code}. diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index 23cf643be8..c8e15d2f1d 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -94,6 +94,7 @@ export const getAuthKey = async (): Promise => { } catch (e) { if ( e instanceof ApiError && + // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison e.httpStatusCode == HttpStatusCode.NotFound ) { throw Error(CustomError.AUTH_KEY_NOT_FOUND); diff --git a/web/apps/auth/src/types/context.ts b/web/apps/auth/src/types/context.ts new file mode 100644 index 0000000000..c5f95ba5c7 --- /dev/null +++ b/web/apps/auth/src/types/context.ts @@ -0,0 +1,17 @@ +import type { AccountsContextT } from "@/accounts/types/context"; +import { THEME_COLOR } from "@ente/shared/themes/constants"; +import { createContext, useContext } from "react"; + +/** + * Properties available via {@link AppContext} to the Auth app's React tree. + */ +type AppContextT = AccountsContextT & { + themeColor: THEME_COLOR; + setThemeColor: (themeColor: THEME_COLOR) => void; +}; + +/** The React {@link Context} available to all pages. */ +export const AppContext = createContext(undefined); + +/** Utility hook to reduce amount of boilerplate in account related pages. */ +export const useAppContext = () => useContext(AppContext)!; diff --git a/web/apps/cast/.eslintrc.js b/web/apps/cast/.eslintrc.js deleted file mode 100644 index b1aff972c9..0000000000 --- a/web/apps/cast/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - extends: ["@/build-config/eslintrc-next"], - ignorePatterns: ["next.config.js", "next-env.d.ts"], -}; diff --git a/web/apps/cast/eslint.config.mjs b/web/apps/cast/eslint.config.mjs new file mode 100644 index 0000000000..06dd5eefb9 --- /dev/null +++ b/web/apps/cast/eslint.config.mjs @@ -0,0 +1 @@ +export { default } from "@/build-config/eslintrc-next-app.mjs"; diff --git a/web/apps/cast/package.json b/web/apps/cast/package.json index a6438eadf4..6e93f92c37 100644 --- a/web/apps/cast/package.json +++ b/web/apps/cast/package.json @@ -5,11 +5,11 @@ "dependencies": { "@/accounts": "*", "@/base": "*", - "@/build-config": "*", "@/media": "*", "@ente/shared": "*" }, "devDependencies": { + "@/build-config": "*", "@types/chromecast-caf-receiver": "^6.0.17" } } diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 1d206867c4..8f17a38abb 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -1,5 +1,4 @@ import log from "@/base/log"; -import { ensure } from "@/utils/ensure"; import { styled } from "@mui/material"; import { FilledCircleCheck } from "components/FilledCircleCheck"; import { useRouter } from "next/router"; @@ -22,7 +21,7 @@ export default function Slideshow() { const loop = async () => { try { - const urlGenerator = imageURLGenerator(ensure(readCastData())); + const urlGenerator = imageURLGenerator(readCastData()!); while (!stop) { const { value: url, done } = await urlGenerator.next(); if (done == true || !url) { diff --git a/web/apps/cast/src/services/cast-data.ts b/web/apps/cast/src/services/cast-data.ts index 367b3e4dbd..85e9dd8451 100644 --- a/web/apps/cast/src/services/cast-data.ts +++ b/web/apps/cast/src/services/cast-data.ts @@ -20,9 +20,11 @@ export const storeCastData = (payload: unknown) => { // localStorage. We don't validate here, we'll validate when we read these // values back in `readCastData`. for (const [key, value] of Object.entries(payload)) { - typeof value == "string" || typeof value == "number" - ? localStorage.setItem(key, value.toString()) - : localStorage.removeItem(key); + if (typeof value == "string" || typeof value == "number") { + localStorage.setItem(key, value.toString()); + } else { + localStorage.removeItem(key); + } } }; diff --git a/web/apps/cast/src/services/detect-type.ts b/web/apps/cast/src/services/detect-type.ts index ca3b93ef2c..717eabecaa 100644 --- a/web/apps/cast/src/services/detect-type.ts +++ b/web/apps/cast/src/services/detect-type.ts @@ -1,4 +1,4 @@ -import { lowercaseExtension } from "@/base/file"; +import { lowercaseExtension } from "@/base/file-name"; import { KnownFileTypeInfos } from "@/media/file-type"; import FileTypeDetect from "file-type"; diff --git a/web/apps/cast/src/services/pair.ts b/web/apps/cast/src/services/pair.ts index 378165eb2b..cc30ef1d1c 100644 --- a/web/apps/cast/src/services/pair.ts +++ b/web/apps/cast/src/services/pair.ts @@ -79,12 +79,6 @@ export const register = async (): Promise => { // Register keypair with museum to get a pairing code. let pairingCode: string | undefined; - // [TODO: spurious while(true) eslint warning]. - // eslint has fixed this spurious warning, but we're not on the latest - // version yet, so add a disable. - // https://github.com/eslint/eslint/pull/18286 - /* eslint-disable no-constant-condition */ - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { try { pairingCode = await castGateway.registerDevice(publicKeyB64); diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 0d9723511c..13e58e9038 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -6,7 +6,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { sharedCryptoWorker } from "@/base/crypto"; -import { nameAndExtension } from "@/base/file"; +import { nameAndExtension } from "@/base/file-name"; import log from "@/base/log"; import { apiURL, customAPIOrigin } from "@/base/origins"; import type { @@ -21,7 +21,6 @@ import { isHEICExtension, needsJPEGConversion } from "@/media/formats"; import { heicToJPEG } from "@/media/heic-convert"; import { decodeLivePhoto } from "@/media/live-photo"; import { shuffled } from "@/utils/array"; -import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -134,7 +133,7 @@ export const imageURLGenerator = async function* (castData: CastData) { // The last to last element is the one that was shown prior to that, // and now can be safely revoked. if (previousURLs.length > 1) - URL.revokeObjectURL(ensure(previousURLs.shift())); + URL.revokeObjectURL(previousURLs.shift()!); previousURLs.push(url); @@ -344,13 +343,13 @@ const downloadFile = async ( ); const cryptoWorker = await sharedCryptoWorker(); - const decrypted = await cryptoWorker.decryptFile( - new Uint8Array(await res.arrayBuffer()), - await cryptoWorker.fromB64( - shouldUseThumbnail + const decrypted = await cryptoWorker.decryptStreamBytes( + { + encryptedData: new Uint8Array(await res.arrayBuffer()), + decryptionHeader: shouldUseThumbnail ? file.thumbnail.decryptionHeader : file.file.decryptionHeader, - ), + }, file.key, ); return new Response(decrypted).blob(); diff --git a/web/apps/payments/.eslintrc.cjs b/web/apps/payments/.eslintrc.cjs deleted file mode 100644 index 99b4b9226c..0000000000 --- a/web/apps/payments/.eslintrc.cjs +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ["@/build-config/eslintrc-vite"], -}; diff --git a/web/apps/payments/eslint.config.mjs b/web/apps/payments/eslint.config.mjs new file mode 100644 index 0000000000..badf668bf1 --- /dev/null +++ b/web/apps/payments/eslint.config.mjs @@ -0,0 +1 @@ +export { default } from "@/build-config/eslintrc-vite-app.mjs"; diff --git a/web/apps/payments/package.json b/web/apps/payments/package.json index 412da2d265..02e87ceaa1 100644 --- a/web/apps/payments/package.json +++ b/web/apps/payments/package.json @@ -18,6 +18,6 @@ "@types/react": "^18", "@types/react-dom": "^18", "@vitejs/plugin-react": "^4.3", - "vite": "^5.4" + "vite": "^5.4.11" } } diff --git a/web/apps/photos/.eslintrc.js b/web/apps/photos/.eslintrc.js deleted file mode 100644 index 4eabc44fff..0000000000 --- a/web/apps/photos/.eslintrc.js +++ /dev/null @@ -1,19 +0,0 @@ -module.exports = { - // When root is set to true, ESLint will stop looking for configuration files in parent directories. - // This is required here to ensure desktop picks the right eslint config, where this app is - // packaged as a submodule. - root: true, - extends: ["@ente/eslint-config"], - parser: "@typescript-eslint/parser", - parserOptions: { - tsconfigRootDir: __dirname, - project: "./tsconfig.json", - }, - ignorePatterns: [ - ".eslintrc.js", - "out", - "thirdparty", - "public", - "next.config.js", - ], -}; diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs new file mode 100644 index 0000000000..afb5066ea3 --- /dev/null +++ b/web/apps/photos/eslint.config.mjs @@ -0,0 +1,40 @@ +import config from "@/build-config/eslintrc-next-app.mjs"; + +export default [ + ...config, + { + ignores: ["thirdparty"], + }, + { + rules: { + /* TODO: + * "This rule requires the `strictNullChecks` compiler option to be + * turned on to function correctly" + */ + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/no-unnecessary-condition": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-return": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-call": "off", + /** TODO: Disabled as we migrate, try to prune these again */ + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-unsafe-enum-comparison": "off", + "@typescript-eslint/no-unnecessary-type-assertion": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/prefer-promise-reject-errors": "off", + "@typescript-eslint/no-useless-constructor": "off", + "@typescript-eslint/require-await": "off", + "@typescript-eslint/no-misused-promises": "off", + "@typescript-eslint/restrict-template-expressions": "off", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-base-to-string": "off", + "@typescript-eslint/restrict-plus-operands": "off", + "@typescript-eslint/no-unused-expressions": "off", + "react-hooks/exhaustive-deps": "off", + "react-refresh/only-export-components": "off", + }, + }, +]; diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json index ad2f3323d2..76c100e46a 100644 --- a/web/apps/photos/package.json +++ b/web/apps/photos/package.json @@ -8,7 +8,6 @@ "@/gallery": "*", "@/media": "*", "@/new": "*", - "@ente/eslint-config": "*", "@ente/shared": "*", "@stripe/stripe-js": "^1.13.2", "bip39": "^3.0.4", @@ -38,7 +37,7 @@ "zxcvbn": "^4.4.2" }, "devDependencies": { - "@ente/eslint-config": "*", + "@/build-config": "*", "@next/bundle-analyzer": "^14.1", "@types/bs58": "^4.0.1", "@types/leaflet": "^1.9", diff --git a/web/apps/photos/public/.well-known/apple-app-site-association b/web/apps/photos/public/.well-known/apple-app-site-association index e05abb216b..ebab4a54da 100644 --- a/web/apps/photos/public/.well-known/apple-app-site-association +++ b/web/apps/photos/public/.well-known/apple-app-site-association @@ -1,4 +1,22 @@ { + "applinks": { + "apps": [], + "details": [ + { + "appIDs": [ + "6Z68YJY9Q2.io.ente.frame" + ], + "paths": [ + "*" + ], + "components": [ + { + "/": "/*" + } + ] + } + ] + }, "webcredentials": { "apps": [ "6Z68YJY9Q2.io.ente.frame", @@ -7,3 +25,4 @@ ] } } + diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index af81944ee2..7b33518870 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -42,6 +42,8 @@ export const AlbumCastDialog: React.FC = ({ useEffect(() => { castGateway.revokeAllTokens(); + // Otherwise tsc complains about unknown property chrome. + // eslint-disable-next-line @typescript-eslint/dot-notation setBrowserCanCast(typeof window["chrome"] !== "undefined"); }, []); @@ -113,7 +115,7 @@ export const AlbumCastDialog: React.FC = ({ setView("choose"); onClose(); }) - .catch((e) => { + .catch((e: unknown) => { log.error("Error casting to TV", e); setView("auto-cast-error"); }); diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index 7958ef6928..77cff0f838 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -535,7 +535,7 @@ const DownloadOption: React.FC< > = ({ isDownloadInProgress, onClick, children }) => ( ) : ( diff --git a/web/apps/photos/src/components/Collections/CollectionNamer.tsx b/web/apps/photos/src/components/Collections/CollectionNamer.tsx index bf64267499..c9429801ba 100644 --- a/web/apps/photos/src/components/Collections/CollectionNamer.tsx +++ b/web/apps/photos/src/components/Collections/CollectionNamer.tsx @@ -1,4 +1,5 @@ import { TitledMiniDialog } from "@/base/components/MiniDialog"; +import log from "@/base/log"; import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; @@ -34,6 +35,7 @@ export default function CollectionNamer({ attributes, ...props }: Props) { attributes.callback(albumName); props.onHide(); } catch (e) { + log.error(e); setFieldError(t("generic_error_retry")); } }; diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 6f17ace58e..e4ea59f925 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -1105,7 +1105,7 @@ const ManageParticipant: React.FC = ({ ), diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 28bc207407..7b1e9bce2c 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -78,7 +78,6 @@ type CollectionsProps = Omit< */ export const GalleryBarAndListHeader: React.FC = ({ shouldHide, - showPeopleSectionButton, mode, onChangeMode, collectionSummaries, @@ -193,7 +192,6 @@ export const GalleryBarAndListHeader: React.FC = ({ <> { errors, handleChange, handleSubmit, - }): JSX.Element => ( + }): React.JSX.Element => (
= ({ // SIDE EFFECTS // ==================== useEffect(() => { - if (!isElectron()) { + if (!isDesktop) { return; } try { diff --git a/web/apps/photos/src/components/FilesDownloadProgress.tsx b/web/apps/photos/src/components/FilesDownloadProgress.tsx index 72b4b9981c..c73699f2d4 100644 --- a/web/apps/photos/src/components/FilesDownloadProgress.tsx +++ b/web/apps/photos/src/components/FilesDownloadProgress.tsx @@ -50,7 +50,7 @@ export const isFilesDownloadCompletedWithErrors = ( export const isFilesDownloadCancelled = ( attributes: FilesDownloadProgressAttributes, ) => { - return attributes && attributes.canceller?.signal?.aborted; + return attributes?.canceller?.signal?.aborted; }; export const FilesDownloadProgress: React.FC = ({ diff --git a/web/apps/photos/src/components/FixCreationTime.tsx b/web/apps/photos/src/components/FixCreationTime.tsx index 91ea1ecb90..35aa71f87f 100644 --- a/web/apps/photos/src/components/FixCreationTime.tsx +++ b/web/apps/photos/src/components/FixCreationTime.tsx @@ -12,7 +12,6 @@ import { FileType } from "@/media/file-type"; import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker"; import downloadManager from "@/new/photos/services/download"; import { extractExifDates } from "@/new/photos/services/exif"; -import { ensure } from "@/utils/ensure"; import { Box, Dialog, @@ -217,7 +216,7 @@ const OptionsForm: React.FC = ({ )}