Compare commits

..

2 Commits

Author SHA1 Message Date
Neeraj Gupta
9e0eb9303f [mob] Start offline mode journey 2025-02-06 11:36:04 +05:30
Neeraj Gupta
e1aee3cfbd temp 2025-02-06 11:36:02 +05:30
342 changed files with 3983 additions and 7012 deletions

View File

@@ -54,12 +54,3 @@ jobs:
packageName: io.ente.auth
releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
track: internal
- name: Notify Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
nodetail: true
title: "🏆 Internal release available for Auth"
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.auth)"
color: 0x800080

View File

@@ -40,7 +40,7 @@ jobs:
- name: Build PlayStore AAB
run: |
flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore
flutter build appbundle --release --flavor playstore
env:
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
@@ -54,12 +54,3 @@ jobs:
packageName: io.ente.photos
releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
track: internal
- name: Notify Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
nodetail: true
title: "🏆 Internal release available for Photos"
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos)"
color: 0x00ff00

View File

@@ -45,7 +45,7 @@ jobs:
- name: Build independent APK
run: |
flutter build apk --dart-define=cronetHttpNoPlay=true --release --flavor independent
flutter build apk --release --flavor independent
mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk
env:
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"

View File

@@ -1,24 +1,27 @@
name: "Publish ghcr (server)"
on:
# Run automatically on 15th of every month, at 05:00 UTC.
schedule:
- cron: '0 5 15 * *'
# Run manually if needed to publish out of schedule.
# Run manually, providing it the commit.
#
# To obtain the commit from the currently deployed museum, do:
# curl -s https://api.ente.io/ping | jq -r '.id'
#
# See server/docs/publish.md for more details.
workflow_dispatch:
inputs:
commit:
description: "Commit to publish the image from"
type: string
required: true
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Determine commit from prod museum
run: |
echo "museum_commit=$(curl -s https://api.ente.io/ping | jq -r .id)" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ env.museum_commit }}
ref: ${{ inputs.commit }}
- name: Build and push
uses: mr-smithers-excellent/docker-build-push@v6
@@ -31,8 +34,8 @@ jobs:
enableBuildKit: true
multiPlatform: true
platform: linux/amd64,linux/arm64
buildArgs: GIT_COMMIT=${{ env.museum_commit }}
tags: ${{ env.museum_commit }}, latest
buildArgs: GIT_COMMIT=${{ inputs.commit }}
tags: ${{ inputs.commit }}, latest
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -21,11 +21,6 @@
"title": "airtm",
"hex": "000000"
},
{
"title": "AJ Bell",
"slug": "aj_bell",
"hex": "c41230"
},
{
"title": "aliyun",
"altNames": [
@@ -35,18 +30,9 @@
{
"title": "Amazon"
},
{
"title": "Ankama",
"slug": "ankama"
},
{
"title": "Anycoin Direct",
"slug": "anycoindirect"
},
{
"title": "Aruba",
"slug": "aruba",
"hex": "ef8a33"
},
{
"title": "AscendEX"
@@ -361,14 +347,6 @@
{
"title": "Estateguru"
},
{
"title": "EVEOnline",
"slug": "eve_online",
"altNames": [
"EVE Online"
],
"hex": "858585"
},
{
"title": "Fastmail"
},
@@ -777,11 +755,6 @@
"altNames": [
"欧易"
]
},
{
"title": "OnShape",
"slug": "onshape",
"hex": "7abb5e"
},
{
"title": "Parqet",
@@ -879,11 +852,6 @@
{
"title": "RealMe",
"slug": "realme"
},
{
"title": "RealVNC",
"slug": "realvnc",
"hex": "488aec"
},
{
"title": "Registro br",
@@ -928,10 +896,6 @@
{
"title": "Samsung"
},
{
"title": "Seafile",
"slug": "seafile"
},
{
"title": "Sendgrid"
},

View File

@@ -1,7 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1507 1556" width="1507" height="1556">
<title>logo_grey-svg</title>
<style>
.s0 { fill: #c41230 }
</style>
<path id="Layer" class="s0" d="m759.6 651c10.6 63.6 7.5 126.3-6.8 185.3-0.6-3.5-0.8-7-1.5-10.6-51.7-311.8-346.3-522.7-657.7-470.8-32.4 5.3-63.6 13.5-93.6 23.9 62.4-54.7 140.3-93.4 228.3-108.1 251.6-41.9 489.5 128.4 531.3 380.3zm84.2 340c-48.7 59.8-107.3 106.4-171.5 140 2.8-3.3 5.7-6.4 8.4-9.7 238.7-292.4 195.6-723.2-96.4-962.3-30.4-24.8-62.2-46.4-95.3-65.2 98.4 12.9 194.5 52.4 276.9 119.9 236 193.2 270.9 541.2 77.9 777.3zm409.3-532.7c66.8 402.8-204.2 783.4-605.6 852.1 313.4-230.6 489-625.2 420.9-1035.7-16.2-97.3-45.1-189.3-84.7-274.7 138.4 110.2 238.1 269.9 269.4 458.3zm241.5 84.5c78.6 473.9-241.4 921.9-714.8 1000.6-195.5 32.6-386.6-3.2-549.2-89.5 98.5 11.2 200.2 9.2 302.8-7.9 537.8-89.4 917.1-559.8 908.4-1089.5 23.9 58.8 41.9 121 52.8 186.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 943 B

View File

@@ -1,5 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720" width="720" height="720">
<title>ankama</title>
<path class="s0" d="m572.3 253.3c-0.3-1.4-0.5-3-1.1-4.4-15.1-46-59.7-76.2-107.9-71.5-31.2 3-55.6 18.9-73.4 44.7-3.8 5.7-7.1 12-10.1 18.4-9.4 19.5-19.5 38.7-24.7 59.7-0.5 1.6-1.1 3.5-1.9 4.9-2.5 4.6-5.5 6.3-10.1 4.1-11.7-5.7-23.8-5.2-36.1-4.4-20.3 1.1-37.9-5.7-52.9-19.5-36.1-33.1-35.3-91 1.9-123 26.8-23.3 43.3-52.1 49.3-86.8 3-16.5 3.3-33.4 0-49.9-0.5-2.7-0.8-5.5-1.4-8.7 7.4-1.4 14.3 0 20.8 2.2 42.8 12.4 69.3 40.6 81.6 82.7 0.5 2.2 1.1 4.4 1.6 6.8 0.8 3 2.7 4.9 5.7 5.5 3.3 0.5 6.3-0.5 7.9-3.5 3-6.3 5.7-12.7 8.2-19.2 3.3-9.7 3.8-10.5 14.3-10.6 58.3-0.8 111.7 15.4 159.4 49 60.5 42.9 98.4 101.4 115.7 173.4 0.8 3.5 0.8 7.6 0.5 11.6-4.6 68-26.8 129.6-68.8 183.5-49 63.1-112.4 104.4-190.7 121.4-72.6 15.8-141.7 4.9-207.1-30.1-1.4-0.8-2.7-1.6-3.8-2.5-0.3 0-0.5-0.8-1.6-2.2 8.7 3 17 5.5 24.7 8.6 13.9 5.7 28.5 8.7 43.6 8.6 14.3 0 28.2 0.8 42.5 0 63.9-3.8 119.5-27.4 166.2-70.7 23.6-21.9 32.8-50.7 30.7-82.4-1.9-32.3-16.5-58.6-42.2-78.3-18.9-14.6-32-33.1-38-56.4-4.1-15.8-4.6-32-0.8-48.2 5.7-24.7 27.7-41.7 52.3-40.6 17 0.8 29.6 9 39.5 22.5 1.4 1.9 2.7 4.1 4.1 6.3 0.5 0 0.8-0.5 1.4-0.8l0.8 0.3v-0.5zm-263.5-55.1c-14.7 0-26.3 12.4-26.3 27.7 0 15.3 11.6 27.4 26.6 27.4 15 0 26-12.4 26-27.1 0-14.7-12-27.9-26.3-27.9v-0.2z"/>
<path class="s0" d="m168.2 314.5c7.4 1.6 14.7 3.3 22.5 4.9 10.9 2.2 14.3 6.8 12 18.1-1.6 8.2-3 16.2-4.9 24.1-0.8 3.5 0 5.7 2.5 8.2 22.8 23.6 50.1 38.4 83.8 43.6-2.2-1.4-3-2.2-4.1-2.7-19.5-10.1-27.7-25.2-25.2-46.6 1.1-9.8 0-19.2-6-27.4-3.8-5.5-9-9.7-13.9-14.3-1.6-1.6-3.8-2.5-6-4.1 5.2-3.3 10.5-3.3 15.4-3 7.4 0.3 14.7 1.4 22.2 3 11.6 2.5 21.7 8.2 30.4 16.2 6.5 6 12.4 12.4 18.7 18.4 13.9 13.6 25.2 12.8 37.2-2.7 7.6-9.8 12.4-21.4 15.8-33.1 3.8-12.7 8.2-24.9 15.4-36.1 7.4-11.6 16.5-21.7 27.4-30.1 9.7-7.4 19.7-6.8 30.7-3.3v9.7c-1.1 27.1 5.2 52.6 19.5 75.9 1.1 1.9 2.2 4.1 3.5 6 4.1 6.3 3.3 12-0.5 18.1-5.5 8.7-13.5 13.2-23.8 13.5h-6.3c-12.7 0-23.3 8.2-26.8 20.6-3 10.9 1.9 23.3 11.7 29.6 10.9 6.8 24.1 5.7 33.4-3 10.1-9.8 16.5-21.7 18.9-35.7 0.3-1.9 0.8-4.1 1.1-6 17.3-0.3 39.8 16.6 48.2 37.2 10.1 24.1 6.3 46.6-10.1 68.8-0.8-5.2-1.4-9.4-2.2-12.8-2.2-9.7-8.6-15.7-18.1-17.7-6.8-1.6-9.8 0-13.6 5.7-1.9 3-3.5 6.3-5.2 9.7-2.5 4.9-4.6 9.8-7.1 14.6-13.6 27.1-34.9 45.8-63.2 55.6-27.1 9.7-55.2 13.2-83.5 10.1-16.5-1.6-29.3-10.5-39.8-23.6 1.9-1.4 3.3-2.7 4.9-3.8 7.4-5.5 12.8-12.7 16.6-20.8 1.1-2.2 1.6-4.9 1.9-7.4 0.5-5.2-2.7-9.4-7.6-9.8-4.6-0.5-8.7 2.5-10.1 7.6-0.8 3.3-1.1 6.5-2.5 9.7-3.8 7.1-9.7 12.8-16.5 17-7.4 4.4-14.6 3.8-20-1.4-5.5-5.5-6-11.7-1.9-19.7 0.3-0.5 0.5-1.4 1.4-3-2.5 1.1-4.1 1.4-5.5 2.2-17.7 10.6-25.8 31.5-19.2 51.5 18.7 56.7-6 119.5-54 151-3.3 2.2-7.1 4.1-11.6 6.8 0-3-0.5-5.2-0.5-7.4-2.5-57.8-30.1-98.9-82.4-122.8-32.8-14.7-53.7-39.5-62.1-74-12.4-50.1 13.9-102.2 61.2-122.8 1.9-0.8 4.1-1.4 7.6-2.7-15.1 28.8-18.7 57.2-9.4 86.8 5.5 17.3 14.7 32 29 45.5 0.3-12.4-0.8-23.6 7.6-33.4 2.7 6.3 5.5 12 8.2 17.7 4.9 10.9 12.7 19.5 23.3 24.9 12 6.3 24.1 6.8 36.5 1.1 8.6-3.8 15.8-9.4 22.2-16.2 8.6-9 8.6-24.9 0.3-35.7-5.5-7.1-8.6-14.7-7.4-23.6 1.9-15.4 15.1-26.8 32-28.2 4.6-0.3 9.4 0 13.6 0.5 4.1 0.8 8.2 2.5 12.7 3.3-3.5-3.8-7.9-6.5-12.7-8.2-8.2-2.7-16.6-3.3-25.2-1.6-14.7 3.3-24.9 16.2-26.3 33.4-0.5 6.3 0 12.8 1.1 19.2 3 16.5-0.8 30.4-13.2 42.8-1.9-1.6-3.8-2.7-5.7-4.4-43.9-37.6-69.6-84.6-75.6-142.2-10.1-95.9 42.5-184.9 130.9-223 28.5-12.4 46-33.8 53.4-63.9 0.3-1.4 0.8-2.7 1.9-3.8 1.9 10.1 1.4 20.3-1.1 30.1-6 24.1-19.5 42.8-41.4 55.3-21.4 12.4-38.4 29-51.5 50.1-2.5 4.1-2.7 7.1 0 10.9 2.7 3.5 5.2 7.6 7.6 11.6 4.4 7.4 3 13.9-4.1 18.9-6.3 4.4-13.2 8.2-19.5 12.7-1.6 1.1-3.5 3.3-3.8 5.2-1.6 18.7-0.5 36.9 5.5 55.3l1.4-0.3-0.2-0.2zm192.8 132c14.3 0 25.8-11.3 25.5-25.2 0-13.6-12-25.8-25.5-25.8-13.5 0-25.2 11.3-25.5 25.5 0 14.3 11.3 25.5 25.2 25.5z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="#FF8300" fill-rule="evenodd" d="M12.1099561,17.3015551 C9.03598293,17.3015551 6.50849391,14.8423766 6.50849391,11.836714 C6.50849391,8.83105139 9.03598293,6.37187289 12.1099561,6.37187289 C15.1839292,6.37187289 17.7114182,8.83105139 17.7114182,11.836714 C17.7114182,14.8423766 15.1839292,17.3015551 12.1099561,17.3015551 L12.1099561,17.3015551 Z M12.1099561,2 C6.50849391,2 2,6.4401834 2,11.836714 C2,17.3015551 6.50849391,21.673428 12.1099561,21.673428 C14.4325135,21.673428 16.5501395,20.9220123 18.2579023,19.6241126 C19.28256,21.3318754 22.2199121,21.673428 22.2199121,21.673428 L22.2199121,11.836714 C22.2199121,6.4401834 17.7114182,2 12.1099561,2 L12.1099561,2 Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 901 B

View File

@@ -1,3 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="198.4" height="198.4" xml:space="preserve">
<path transform="translate(0, 60)" d="M 0,0 0,13.88 10.97,13.88 10.97,10.31 60.69,10.31 60.69,0 0,0 z M 65.84,0 99.22,58.09 132.6,0 120.7,0 C 120.7,0 100.5,34.91 99.22,37.16 97.92,34.91 77.75,0 77.75,0 L 65.84,0 z M 137.8,0 137.8,13.88 148.7,13.88 148.7,10.31 198.4,10.31 198.4,0 137.8,0 z M 0,19.12 0,29.47 60.69,29.47 60.69,19.12 0,19.12 z M 137.8,19.12 137.8,29.47 198.4,29.47 198.4,19.12 137.8,19.12 z M 0,34.66 0,48.59 60.69,48.59 60.69,38.25 10.97,38.25 10.97,34.66 0,34.66 z M 137.8,34.66 137.8,48.59 198.4,48.59 198.4,38.25 148.7,38.25 148.7,34.66 137.8,34.66 z M 42.19,69.72 C 41.32,69.72 40.71,69.89 40.41,70.19 40.1,70.49 39.97,71.03 39.97,71.84 L 39.97,76.56 C 39.97,77.38 40.1,77.93 40.41,78.22 40.71,78.52 41.32,78.66 42.19,78.66 L 48.72,78.66 C 49.59,78.66 50.19,78.52 50.5,78.22 50.8,77.93 50.97,77.38 50.97,76.56 L 50.97,71.84 C 50.97,71.03 50.8,70.49 50.5,70.19 50.19,69.89 49.59,69.72 48.72,69.72 L 42.19,69.72 z M 64.37,69.72 64.37,78.66 66.25,78.66 66.25,73.84 C 66.25,73.66 66.23,73.43 66.22,73.19 66.2,72.94 66.18,72.69 66.16,72.41 66.26,72.53 66.38,72.67 66.5,72.78 66.62,72.89 66.75,73.01 66.91,73.16 L 73.47,78.66 74.88,78.66 74.88,69.72 73.03,69.72 73.03,74.41 C 73.03,74.52 73.05,74.7 73.06,74.91 73.07,75.11 73.09,75.47 73.12,75.97 72.99,75.81 72.82,75.66 72.66,75.5 72.49,75.35 72.31,75.18 72.09,75 L 65.81,69.72 64.37,69.72 z M 88.53,69.72 88.53,78.66 97.31,78.66 97.31,77 90.59,77 90.59,69.72 88.53,69.72 z M 109.4,69.72 109.4,78.66 111.5,78.66 111.5,69.72 109.4,69.72 z M 125.1,69.72 125.1,78.66 127,78.66 127,73.84 C 127,73.66 127,73.43 126.9,73.19 126.9,72.94 126.9,72.69 126.9,72.41 127,72.53 127.1,72.67 127.2,72.78 127.3,72.89 127.5,73.01 127.6,73.16 L 134.2,78.66 135.6,78.66 135.6,69.72 133.8,69.72 133.8,74.41 C 133.8,74.52 133.8,74.7 133.8,74.91 133.8,75.11 133.8,75.47 133.8,75.97 133.7,75.81 133.6,75.66 133.4,75.5 133.2,75.35 133,75.18 132.8,75 L 126.5,69.72 125.1,69.72 z M 149.3,69.72 149.3,78.66 158.5,78.66 158.5,77 151.3,77 151.3,74.78 155.4,74.78 155.4,73.25 151.3,73.25 151.3,71.25 158.4,71.25 158.4,69.72 149.3,69.72 z M 42.03,71.31 48.87,71.31 48.87,77 42.03,77 42.03,71.31 z" /></svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -1,13 +0,0 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 120" width="180" height="120">
<title>seafile</title>
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,114.369,-177.525,0,89.989,2.834)">
<stop offset="0" stop-color="#fad956"/>
<stop offset="1" stop-color="#ffa10f"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
</style>
<path class="s0" d="m1.2 52.8c0-3 2.4-5.4 5.4-5.4 1.4 0 2.7 0.6 3.6 1.5q0-0.7 0-1.4c0-9.9 8-17.9 17.9-17.9 2.5 0 4.9 0.5 7.1 1.5q0-0.8 0-1.5c0-14.8 12-26.8 26.8-26.8 14.7 0 26.6 11.9 26.8 26.6-4.8 4.2-8.7 9.6-11.2 15.7-4.8-3-10.4-4.8-16.5-4.8-12.4 0-23.2 7.1-28.3 17.8h-19.1-7.1c-3 0-5.4-2.4-5.4-5.3zm141.2-16c-6.6-6.7-15.8-10.8-25.9-10.8-18.5 0-33.8 13.7-36.3 31.5-4.5-6.1-11.8-10-20-10-13.8 0-25 11.2-25 25 0 4 0.9 7.8 2.6 11.2-8.7 1.7-15.1 8.5-15.1 16.5 0 9.4 8.8 17 19.7 17 4.7 0 9.1-1.5 12.6-4l40.2-39.5c4.4-4.1 10.3-6.6 16.8-6.6 13.6 0 24.7 10.9 25.1 24.4q0 0-0.1-0.1c0.2 4-1.8 8.1-5.7 10.3-5.3 3.1-12 1.4-15-3.7-2.9-5.1-1-11.7 4.4-14.8q1.9-1.1 3.9-1.4-1.8-0.4-3.6-0.4c-9.9 0-17.9 8-17.9 17.9 0 9.9 8 17.9 17.9 17.9q0.6 0 1.3-0.1l0.5-0.1h35.1v0.2c10.7-0.5 20.9-10.4 20.9-22.5 0-12.3-10.6-22.4-22.9-22.4q-0.1 0-0.1 0c-2 3.6-4.4 5.7-7.1 7.9 2.8-5.2 4.5-11.2 4.5-17.6-0.1-10.1-4.2-19.2-10.8-25.8z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -48,7 +48,6 @@
"nextTotpTitle": "التالي",
"deleteCodeTitle": "حذف الرمز؟",
"deleteCodeMessage": "هل أنت متأكد من أنك تريد حذف هذه الشيفرة؟ هذا الإجراء لا رجعة فيه.",
"trash": "سلة المهملات",
"viewLogsAction": "عرض السجلات",
"sendLogsDescription": "سوف يُرسل هذا السجلات لنا لمساعدتنا على تصحيح مشكلتك. بينما نتخذ الاحتياطات لضمان عدم تسجيل المعلومات الحساسة، نشجعك على رؤية هذه السجلات قبل مشاركتها.",
"preparingLogsTitle": "جارٍ إعداد السجلات...",
@@ -198,8 +197,6 @@
"enterDetailsManually": "أدخل التفاصيل يدوياً",
"edit": "تعديل",
"share": "مشاركة",
"shareCodes": "شارك الرموز",
"restore": "استعادة",
"copiedToClipboard": "تم النسخ إلى الحافظة",
"copiedNextToClipboard": "تم نسخ الرموز التالية إلى الحافظة",
"error": "خطأ",
@@ -251,10 +248,6 @@
"areYouSureYouWantToLogout": "هل أنت متأكد من أنك تريد تسجيل الخروج؟",
"yesLogout": "نعم، تسجيل الخروج",
"exit": "خروج",
"theme": "السمة",
"lightTheme": "فاتح",
"darkTheme": "داكن",
"systemTheme": "النظام",
"verifyingRecoveryKey": "التحقق من مفتاح الاسترداد...",
"recoveryKeyVerified": "تم التحقق من مفتاح الاسترداد",
"recoveryKeySuccessBody": "رائع! مفتاح الاسترداد الخاص بك صالح. شكرا لك على التحقق.\n\nيرجى تذكر الاحتفاظ بنسخة احتياطية من مفتاح الاسترداد بشكل آمن.",
@@ -325,9 +318,6 @@
}
}
},
"manualSort": "مخصّص",
"mostFrequentlyUsed": "مستخدم بكثرة",
"mostRecentlyUsed": "مستخدمة مؤخراً",
"activeSessions": "الجلسات النشطة",
"somethingWentWrongPleaseTryAgain": "حدث خطأ ما، يرجى المحاولة مرة أخرى",
"thisWillLogYouOutOfThisDevice": "سيؤدي هذا إلى تسجيل خروجك من هذا الجهاز!",
@@ -360,7 +350,6 @@
"sigInBackupReminder": "يرجى تصدير الرموز الخاصة بك للتأكد من أن لديك نسخة احتياطية يمكنك استعادتها منها.",
"offlineModeWarning": "لقد اخترت المضي قدما بدون نسخ احتياطية. يرجى أخذ نسخ احتياطية يدوية للتأكد من سلامة الرموز الخاصة بك.",
"showLargeIcons": "إظهار أيقونات كبيرة",
"compactMode": "الوضع المضغوط",
"shouldHideCode": "إخفاء الرموز",
"doubleTapToViewHiddenCode": "يمكنك النقر مرتين على أي عنصر لعرض الرمز",
"focusOnSearchBar": "التركيز على البحث عند بدء التطبيق",
@@ -479,11 +468,5 @@
"pinLock": "قفل رقم التعريف الشخصي",
"enterPin": "أدخل رقم التعريف الشخصي",
"setNewPin": "عين رقم تعريف شخصي جديد",
"importFailureDescNew": "تعذر إعراب الملف المنتقى.",
"duplicateCodes": "رموز مكررة",
"noDuplicates": "✨ لا تكرارات",
"youveNoDuplicateCodesThatCanBeCleared": "ليس لديك رموز مكررة يمكن مسحها",
"deselectAll": "ألغِ تحديد الكل",
"selectAll": "حدد الكل",
"deleteDuplicates": "احذف التكرار"
"importFailureDescNew": "تعذر إعراب الملف المنتقى."
}

View File

@@ -88,8 +88,6 @@
"useRecoveryKey": "Usa la clau de recuperació",
"incorrectPasswordTitle": "Contrasenya incorrecta",
"welcomeBack": "Benvingut de nou!",
"emailAlreadyRegistered": "El correu electrònic ja està registrat.",
"emailNotRegistered": "El correu electrònic no està registrat.",
"madeWithLoveAtPrefix": "fet amb ❤️ a ",
"supportDevs": "Subscriu-te a <bold-green>ente</bold-green> per donar-nos suport",
"supportDiscount": "Usa el codi de descompte \"AUTH\" per obtenir un 10% de descompte el primer any",
@@ -504,13 +502,5 @@
"deselectAll": "Desselecciona-ho tot",
"selectAll": "Seleccionar-ho tot",
"deleteDuplicates": "Elimina duplicats",
"plainHTML": "HTML pla",
"tellUsWhatYouThink": "Digueu-nos què us sembla",
"dropReview": "Deixa una ressenya a l'App/Play Store",
"supportEnte": "Donar suport a <bold-green>ente</bold-green>",
"giveUsAStarOnGithub": "Dona'ns una estrella a Github",
"free5GB": "5 GB gratuïts a <bold-green>ente</bold-green> Photos",
"loginWithAuthAccount": "Inicieu sessió amb el vostre compte Auth",
"freeStorageOffer": "10% de descompte a <bold-green>ente</bold-green> photos",
"freeStorageOfferDescription": "Utilitzeu el codi \"AUTH\" per obtenir un 10% de descompte el primer any"
"plainHTML": "HTML pla"
}

View File

@@ -88,8 +88,6 @@
"useRecoveryKey": "Wiederherstellungsschlüssel verwenden",
"incorrectPasswordTitle": "Falsches Passwort",
"welcomeBack": "Willkommen zurück!",
"emailAlreadyRegistered": "E-Mail ist bereits registriert.",
"emailNotRegistered": "E-Mail-Adresse nicht registriert.",
"madeWithLoveAtPrefix": "gemacht mit ❤️ bei ",
"supportDevs": "Bei <bold-green>ente</bold-green> registrieren, um das Projekt zu unterstützen",
"supportDiscount": "Benutzen Sie den Rabattcode \"AUTH\" für 10 % Rabatt im ersten Jahr",
@@ -257,8 +255,6 @@
"areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?",
"yesLogout": "Ja ausloggen",
"exit": "Schließen",
"theme": "Theme",
"systemTheme": "System",
"verifyingRecoveryKey": "Verifiziere Wiederherstellungsschlüssel...",
"recoveryKeyVerified": "Wiederherstellungsschlüssel verifiziert",
"recoveryKeySuccessBody": "Großartig! Ihr Wiederherstellungsschlüssel ist gültig. Vielen Dank für die Verifizierung.\n\nBitte denken sie daran, dass sie ihren Wiederherstellungsschlüssel sicher aufbewahren.",
@@ -329,7 +325,6 @@
}
}
},
"manualSort": "Benutzerdefiniert",
"activeSessions": "Aktive Sitzungen",
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
"thisWillLogYouOutOfThisDevice": "Dadurch wirst du von diesem Gerät abgemeldet!",
@@ -483,9 +478,5 @@
"setNewPin": "Neue PIN festlegen",
"importFailureDescNew": "Die ausgewählte Datei konnte nicht verarbeitet werden.",
"appLockNotEnabled": "App-Sperre nicht aktiviert",
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre",
"duplicateCodes": "Doppelte Codes",
"noDuplicates": "✨ Keine Duplikate",
"deselectAll": "Alle abwählen",
"selectAll": "Alles auswählen"
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre"
}

View File

@@ -504,12 +504,5 @@
"deselectAll": "Deseleccionar todo",
"selectAll": "Seleccionar todo",
"deleteDuplicates": "Eliminar duplicados",
"plainHTML": "HTML plano",
"tellUsWhatYouThink": "Cuéntanos cuál es su opinión",
"dropReview": "Danos una reseña en la App/Play Store",
"supportEnte": "Apoya a <bold-green>ente</bold-green>",
"giveUsAStarOnGithub": "Danos una estrella en GitHub",
"free5GB": "5 GB gratis en <bold-green>ente</bold-green> Fotos",
"freeStorageOffer": "10% de descuento en <bold-green>ente</bold-green> fotos",
"freeStorageOfferDescription": "Usa el cupón \"AUTH\" para obtener un 10% de descuento en el primer año"
"plainHTML": "HTML plano"
}

View File

@@ -8,13 +8,13 @@
},
"onBoardingGetStarted": "प्रारंभ करें",
"setupFirstAccount": "अपना पहला अकाउंट सेटअप करें",
"importScanQrCode": "एक QR कोड स्कैन करें",
"importScanQrCode": "QR कोड स्कैन करें",
"qrCode": "QR कोड",
"importEnterSetupKey": "",
"importAccountPageTitle": "अकाउंट विवरण डालें",
"incorrectDetails": "ग़लत विवरण",
"pleaseVerifyDetails": "कृपया विवरण सत्यापित करें और पुनः प्रयास करें",
"codeIssuerHint": "ारीकर्ता",
"codeIssuerHint": "ारीकर्ता",
"codeSecretKeyHint": "सीक्रेट कुंजी",
"secret": "सीक्रेट",
"all": "सभी",
@@ -39,10 +39,6 @@
"pleaseLoginAgain": "कृपया फिर से लॉगिन करें",
"loggingOut": "लॉग आउट हो रहा है...",
"saveAction": "सेव करें",
"deleteCodeMessage": "क्या आप वाकई इस कोड को हटाना चाहते हैं? इस क्रिया को वापस नहीं किया जा सकता",
"trashCode": "?",
"trashCodeMessage": "क्या आप वाकई {account} के लिए कोड नष्ट करना चाहते हैं?",
"trash": "नष्ट करें",
"viewLogsAction": "लॉग देखें",
"preparingLogsTitle": "लॉग तैयार किये जा रहे हैं...",
"emailLogsTitle": "लॉग ईमेल करें",
@@ -54,7 +50,6 @@
}
}
},
"copyEmailAction": "ईमेल कॉपी करें",
"exportLogsAction": "लॉग एक्सपोर्ट करें",
"reportABug": "बग रिपोर्ट करें",
"reportBug": "बग रिपोर्ट करें",
@@ -84,24 +79,5 @@
"cancel": "रद्द करें",
"yes": "हाँ",
"no": "नहीं",
"settings": "सेटिंग",
"pleaseTryAgain": "कृपया पुन: प्रयास करें",
"newUser": "एंटे में नए उपयोगकर्ता",
"delete": "हटाएं",
"enterYourPasswordHint": "अपना पासवर्ड दर्ज करें",
"forgotPassword": "पासवर्ड भूल गए",
"oops": "ओह",
"suggestFeatures": "विशेषताएं सुझाएं",
"faq": "अक्सर किये गए सवाल",
"somethingWentWrongMessage": "कुछ गड़बड़ हुई है, कृपया दोबारा प्रयास करें",
"leaveFamily": "परिवार छोड़ें",
"leaveFamilyMessage": "क्या आप सच में परिवार प्लान छोड़ना चाहते हैं?",
"inFamilyPlanMessage": "आप परिवार प्लान पर हैं!",
"hintForMobile": "कोड को संपादित करने या हटाने के लिए उसे लंबी देर तक दबाए।",
"hintForDesktop": "कोड को संपादित करने या हटाने के लिए उस पर राइट क्लिक करें।",
"scan": "स्कैन करें",
"scanACode": "कोड स्कैन करें",
"verify": "सत्यापित करें",
"verifyEmail": "ईमेल सत्यापित करें",
"twoFactorAuthTitle": "दो-चरणीय प्रमाणीकरण |"
"settings": "सेटिंग"
}

View File

@@ -504,13 +504,5 @@
"deselectAll": "Összes kijelölés megszüntetése",
"selectAll": "Összes kijelölése",
"deleteDuplicates": "Ismétlődések törlése",
"plainHTML": "Sima HTML kód",
"tellUsWhatYouThink": "Mondja el mit gondol",
"dropReview": "Írjon véleményt az App/Play Store-ban",
"supportEnte": "Támogassa <bold-green>ente <bold-green>",
"giveUsAStarOnGithub": "Adj nekünk egy csillagot a Githubon",
"free5GB": "5GB ingyen <bold-green>ente <bold-green> Photos",
"loginWithAuthAccount": "Jelentkezzen be Auth fiókjával",
"freeStorageOffer": "10% kedvezmény on <bold-green>ente<bold-green> photos",
"freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben"
"plainHTML": "Sima HTML kód"
}

View File

@@ -499,18 +499,7 @@
"appLockOfflineModeWarning": "バックアップなしで進むことを選択しました。アプリロックを忘れると、データにアクセスできなくなります。",
"duplicateCodes": "重複コード",
"noDuplicates": "✨ 重複なし",
"youveNoDuplicateCodesThatCanBeCleared": "削除できる重複コードはありません",
"deduplicateCodes": "重複コード",
"deselectAll": "すべての選択を解除",
"selectAll": "すべて選択",
"deleteDuplicates": "重複を削除",
"plainHTML": "Plain HTML",
"tellUsWhatYouThink": "ご意見をお聞かせください",
"dropReview": "App/Playストアにレビューを投稿する",
"supportEnte": "<bold-green>ente</bold-green>をサポートする",
"giveUsAStarOnGithub": "Githubで星をつける",
"free5GB": "<bold-green>ente</bold-green>フォトで5GB無料",
"loginWithAuthAccount": "認証アカウントでログイン",
"freeStorageOffer": "<bold-green>ente</bold-green>の写真が10%オフ",
"freeStorageOfferDescription": "クーポンコード \"AUTH\" の使用で初年度が10%オフになります"
"loginWithAuthAccount": "認証アカウントでログイン"
}

View File

@@ -1,28 +1 @@
{
"blog": "ബ്ലോഗ്",
"verifyPassword": "പാസ്‌വേഡ് സ്ഥിരീകരിക്കുക",
"recreatePassword": "പാസ്‌വേഡ് പുനഃസൃഷ്ടിക്കുക",
"incorrectPasswordTitle": "തെറ്റായ പാസ്‌വേഡ്",
"welcomeBack": "വീണ്ടും സ്വാഗതം!",
"emailAlreadyRegistered": "ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.",
"emailNotRegistered": "ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.",
"changeEmail": "ഇമെയിൽ മാറ്റുക",
"changePassword": "പാസ്സ്‌വേർഡ് മാറ്റുക",
"ok": "ശരി",
"cancel": "റദ്ദാക്കുക",
"yes": "അതെ",
"no": "അല്ല",
"email": "ഇമെയിൽ",
"somethingWentWrongMessage": "എന്തോ കുഴപ്പമുണ്ടായി, ദയവായി വീണ്ടും ശ്രമിക്കുക",
"inFamilyPlanMessage": "നിങ്ങൾ ഒരു ഫാമിലി പ്ലാനിലാണ്!",
"scan": "സ്കാൻ ചെയ്യുക",
"scanACode": "കോഡ് സ്കാൻ ചെയ്യുക",
"verify": "പരിശോധിക്കുക",
"verifyEmail": "ഇമെയിൽ സ്ഥിരീകരിക്കുക",
"enterCodeHint": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക",
"twoFactorAuthTitle": "ടു-ഫാക്ടർ ആധികാരികത",
"createNewAccount": "പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക",
"confirmPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
"language": "ഭാഷ",
"security": "സുരക്ഷ"
}
{}

View File

@@ -88,8 +88,6 @@
"useRecoveryKey": "Kurtarma anahtarını kullan",
"incorrectPasswordTitle": "Yanlış şifre",
"welcomeBack": "Tekrar hoş geldiniz!",
"emailAlreadyRegistered": "E-posta zaten kayıtlı.",
"emailNotRegistered": "E-posta kayıtlı değil.",
"madeWithLoveAtPrefix": "❤️ ile şurada yapılmıştır ",
"supportDevs": "Bu projeyi desteklemek için <bold-green>ente</bold-green> kanalına abone olun",
"supportDiscount": "İlk yılda %10 indirim için \"AUTH\" kupon kodunu kullanın",
@@ -335,7 +333,6 @@
}
},
"manualSort": "Özel",
"editOrder": "Sıralamayı düzenle",
"mostFrequentlyUsed": "Sık kullanılan",
"mostRecentlyUsed": "Son kullanılan",
"activeSessions": "Aktif oturumlar",
@@ -504,6 +501,5 @@
"deselectAll": "Tümünün seçimini kaldır",
"selectAll": "Tümünü seç",
"deleteDuplicates": "Yinelenenleri sil",
"plainHTML": "Sade HTML",
"supportEnte": "<bold-Green>Ente</bold-Green>'yi destekle"
"plainHTML": "Sade HTML"
}

View File

@@ -6,7 +6,7 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:window_manager/window_manager.dart';
class WindowListenerService {
static const double minWindowHeight = 600.0;
static const double minWindowHeight = 320.0;
static const double minWindowWidth = 800.0;
static const double maxWindowHeight = 8192.0;
static const double maxWindowWidth = 8192.0;

View File

@@ -15,7 +15,7 @@ import (
"strings"
)
var AppVersion = "0.2.3"
var AppVersion = "0.2.2"
func main() {
cliConfigDir, err := GetCLIConfigDir()
@@ -50,21 +50,18 @@ func main() {
}
}
// Define a set of commands that do not require KeyHolder or cli initialisation.
skipInitCommands := map[string]struct{}{"version": {}, "docs": {}, "help": {}}
// Define a set of commands that do not require KeyHolder initialisation.
skipKeyHolderCommands := map[string]struct{}{"version": {}, "docs": {}, "help": {}}
var keyHolder *secrets.KeyHolder
// Only initialise KeyHolder if the command isn't in the skip list.
shouldInit := len(os.Args) > 1
if len(os.Args) > 1 {
if _, skip := skipInitCommands[os.Args[1]]; skip {
shouldInit = false
if _, skip := skipKeyHolderCommands[os.Args[1]]; !skip {
keyHolder = secrets.NewKeyHolder(secrets.GetOrCreateClISecret())
}
}
if shouldInit {
keyHolder = secrets.NewKeyHolder(secrets.GetOrCreateClISecret())
}
ctrl := pkg.ClICtrl{
Client: api.NewClient(api.Params{
Debug: viper.GetBool("log.http"),
@@ -74,10 +71,16 @@ func main() {
KeyHolder: keyHolder,
}
if len(os.Args) == 1 {
// If no arguments are passed, show help
os.Args = append(os.Args, "help")
err = ctrl.Init()
if err != nil {
panic(err)
}
defer func() {
if err := db.Close(); err != nil {
panic(err)
}
}()
if len(os.Args) == 2 && os.Args[1] == "docs" {
log.Println("Generating docs")
err = cmd.GenerateDocs()
@@ -86,16 +89,9 @@ func main() {
}
return
}
if shouldInit {
err = ctrl.Init()
if err != nil {
panic(err)
}
defer func() {
if err := db.Close(); err != nil {
panic(err)
}
}()
if len(os.Args) == 1 {
// If no arguments are passed, show help
os.Args = append(os.Args, "help")
}
if os.Args[1] == "version" && viper.GetString("endpoint.api") != constants.EnteApiUrl {
log.Printf("Custom endpoint: %s\n", viper.GetString("endpoint.api"))
@@ -124,10 +120,10 @@ func initConfig(cliConfigDir string) {
func GetCLIConfigDir() (string, error) {
var configDir = os.Getenv("ENTE_CLI_CONFIG_DIR")
if configDir == "" {
// for backward compatibility, check for ENTE_CLI_CONFIG_PATH
configDir = os.Getenv("ENTE_CLI_CONFIG_PATH")
}
if configDir == "" {
// for backward compatibility, check for ENTE_CLI_CONFIG_PATH
configDir = os.Getenv("ENTE_CLI_CONFIG_PATH")
}
if configDir != "" {
// remove trailing slash (for all OS)

View File

@@ -1,14 +1,11 @@
# CHANGELOG
## v1.7.10 (Unreleased)
- .
## v1.7.9
## v1.7.9 (Unreleased)
- Light mode.
- Faster and more stable thumbnail generation.
- Support `.supplemental-metadata` JSON files in Google Takeout.
- .
## v1.7.8

View File

@@ -38,8 +38,8 @@
</branding>
<releases>
<release version="1.7" date="2025-01-13">
<url type="details">https://github.com/ente-io/photos-desktop/releases</url>
<release version="1.7.8" date="2025-01-13">
<url type="details">https://github.com/ente-io/photos-desktop/releases/tag/v1.7.8</url>
</release>
</releases>
</component>

View File

@@ -1,6 +1,6 @@
{
"name": "ente",
"version": "1.7.10-beta",
"version": "1.7.9-beta",
"private": true,
"description": "Desktop client for Ente Photos",
"repository": "github:ente-io/photos-desktop",
@@ -49,7 +49,7 @@
"ajv": "^8.17.1",
"concurrently": "^9.1.2",
"cross-env": "^7.0.3",
"electron": "^34.1.1",
"electron": "^34.0.2",
"electron-builder": "^26.0.0",
"eslint": "^9",
"prettier": "3.4.2",

View File

@@ -152,7 +152,7 @@ const main = () => {
void mainWindow.loadURL(rendererURL);
// Continue on with the rest of the startup sequence.
Menu.setApplicationMenu(createApplicationMenu(mainWindow));
Menu.setApplicationMenu(await createApplicationMenu(mainWindow));
setupTrayItem(mainWindow);
setupAutoUpdater(mainWindow);
@@ -326,6 +326,8 @@ const attachProcessHandlers = () => {
*/
const waitForRendererDevServer = () => wait(1000);
const wipDesktopCustomTitlebar = process.env.ENTE_WIP_TITLEBAR == "1";
/**
* Create an return the {@link BrowserWindow} that will form our app's UI.
*
@@ -362,18 +364,12 @@ const createMainWindow = () => {
// do it (Step 2) unconditionally (i.e., on macOS too).
//
// https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar
//
// Note that by default on Windows, the color of the WCO title bar
// overlay (three buttons - minimize, maximize, close - on the top
// right) is static, and unlike Linux, doesn't adapt to the theme /
// content. Explicitly choosing a dark background, while it won't work
// always (if the user's theme is light), is better than picking a light
// background since the main image viewer is always dark.
titleBarStyle: "hidden",
titleBarOverlay:
process.platform == "win32"
? { color: "black", symbolColor: "#cdcdcd" }
: true,
...(wipDesktopCustomTitlebar
? {
titleBarStyle: "hidden",
titleBarOverlay: true,
}
: {}),
// The color to show in the window until the web content gets loaded.
// https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property
//

View File

@@ -24,7 +24,6 @@ import {
updateAndRestart,
updateOnNextRestart,
} from "./services/app-update";
import autoLauncher from "./services/auto-launcher";
import {
openDirectory,
openLogDirectory,
@@ -118,10 +117,6 @@ export const attachIPCHandlers = () => {
setLastShownChangelogVersion(version),
);
ipcMain.handle("isAutoLaunchEnabled", () => autoLauncher.isEnabled());
ipcMain.handle("toggleAutoLaunch", () => autoLauncher.toggleAutoLaunch());
// - App update
ipcMain.on("updateAndRestart", () => updateAndRestart());

View File

@@ -7,14 +7,17 @@ import {
} from "electron";
import { allowWindowClose } from "../main";
import { forceCheckForAppUpdates } from "./services/app-update";
import autoLauncher from "./services/auto-launcher";
import { openLogDirectory } from "./services/dir";
import { userPreferences } from "./stores/user-preferences";
/** Create and return the entries in the app's main menu bar */
export const createApplicationMenu = (mainWindow: BrowserWindow) => {
export const createApplicationMenu = async (mainWindow: BrowserWindow) => {
// The state of checkboxes
//
// Whenever the menu is redrawn the current value of these variables is used
// to set the checked state for the various settings checkboxes.
let isAutoLaunchEnabled = await autoLauncher.isEnabled();
let shouldHideDockIcon = !!userPreferences.get("hideDockIcon");
const macOSOnly = (options: MenuItemConstructorOptions[]) =>
@@ -22,6 +25,16 @@ export const createApplicationMenu = (mainWindow: BrowserWindow) => {
const handleCheckForUpdates = () => forceCheckForAppUpdates(mainWindow);
const handleViewChangelog = () =>
void shell.openExternal(
"https://github.com/ente-io/ente/blob/main/desktop/CHANGELOG.md",
);
const toggleAutoLaunch = () => {
void autoLauncher.toggleAutoLaunch();
isAutoLaunchEnabled = !isAutoLaunchEnabled;
};
const toggleHideDockIcon = () => {
// Persist
userPreferences.set("hideDockIcon", !shouldHideDockIcon);
@@ -32,6 +45,13 @@ export const createApplicationMenu = (mainWindow: BrowserWindow) => {
const handleHelp = () =>
void shell.openExternal("https://help.ente.io/photos/");
const handleSupport = () =>
void shell.openExternal("mailto:support@ente.io");
const handleBlog = () => void shell.openExternal("https://ente.io/blog/");
const handleViewLogs = () => void openLogDirectory();
return Menu.buildFromTemplate([
{
label: "Ente Photos",
@@ -47,21 +67,31 @@ export const createApplicationMenu = (mainWindow: BrowserWindow) => {
label: "Check for Updates...",
click: handleCheckForUpdates,
},
{
label: "View Changelog",
click: handleViewChangelog,
},
{ type: "separator" },
...macOSOnly([
{
label: "Preferences",
submenu: [
{
label: "Preferences",
submenu: [
{
label: "Open Ente on Startup",
type: "checkbox",
checked: isAutoLaunchEnabled,
click: toggleAutoLaunch,
},
...macOSOnly([
{
label: "Hide Dock Icon",
type: "checkbox",
checked: shouldHideDockIcon,
click: toggleHideDockIcon,
},
],
},
]),
]),
],
},
{ type: "separator" },
...macOSOnly([
@@ -139,6 +169,20 @@ export const createApplicationMenu = (mainWindow: BrowserWindow) => {
label: "Ente Help",
click: handleHelp,
},
{ type: "separator" },
{
label: "Support",
click: handleSupport,
},
{
label: "Product Updates",
click: handleBlog,
},
{ type: "separator" },
{
label: "View Logs",
click: handleViewLogs,
},
],
},
]);
@@ -150,6 +194,7 @@ export const createApplicationMenu = (mainWindow: BrowserWindow) => {
*/
export const createTrayContextMenu = (mainWindow: BrowserWindow) => {
const handleOpen = () => {
mainWindow.maximize();
mainWindow.show();
};

View File

@@ -122,10 +122,6 @@ const lastShownChangelogVersion = () =>
const setLastShownChangelogVersion = (version: number) =>
ipcRenderer.invoke("setLastShownChangelogVersion", version);
const isAutoLaunchEnabled = () => ipcRenderer.invoke("isAutoLaunchEnabled");
const toggleAutoLaunch = () => ipcRenderer.invoke("toggleAutoLaunch");
const onMainWindowFocus = (cb: (() => void) | undefined) => {
ipcRenderer.removeAllListeners("mainWindowFocus");
if (cb) ipcRenderer.on("mainWindowFocus", cb);
@@ -351,8 +347,6 @@ contextBridge.exposeInMainWorld("electron", {
saveMasterKeyB64,
lastShownChangelogVersion,
setLastShownChangelogVersion,
isAutoLaunchEnabled,
toggleAutoLaunch,
onMainWindowFocus,
onOpenEnteURL,

View File

@@ -1289,10 +1289,10 @@ electron-updater@^6.4.0:
semver "^7.6.3"
tiny-typed-emitter "^2.1.0"
electron@^34.1.1:
version "34.1.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-34.1.1.tgz#1fc766e406401834fedb9747c4ca58671d9a1e46"
integrity sha512-1aDYk9Gsv1/fFeClMrxWGoVMl7uCUgl1pe26BiTnLXmAoqEXCa3f3sCKFWV+cuDzUjQGAZcpkWhGYTgWUSQrLA==
electron@^34.0.2:
version "34.0.2"
resolved "https://registry.yarnpkg.com/electron/-/electron-34.0.2.tgz#84432ab1efa165e260ce943c472879228b620573"
integrity sha512-u3F+DSUlg9NaGS+9qnYmSRN8VjAnc3LJDDk1ye1uISJnh4gjG76y3681qLowsPMx4obvCP2eBINnmbLo0yT5WA==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"

View File

@@ -138,10 +138,6 @@ export const sidebar = [
text: "Machine Learning",
link: "/photos/faq/machine-learning",
},
{
text: "Video Streaming",
link: "/photos/faq/video-streaming",
},
],
},
{

View File

@@ -1,63 +0,0 @@
---
title: Video Streaming FAQ
description:
Frequently asked questions about Ente's Video Streaming feature
---
# Video Streaming
> [!NOTE]
>
> Video streaming is available in beta on mobile apps starting v0.9.98.
### How to enable video streaming?
- Open Settings -> General -> Advanced
- Switch on the toggle for `Video streaming`
### What happens when I enable video streaming?
Enabling video streaming will start processing videos captured in the last 30
days, generating streams for each. Both local and remote videos will be
processed, so this may consume bandwidth for downloading of remote files and
uploading of the generated streams.
### How can I view video streams?
Settings -> Backup > Backup status will show details regarding the processing
status for videos. Processed videos will have a green play button next to them.
You can open these videos by tapping on them.
Processed videos will show a `Play stream` button, clicking which will load and
play the stream.
Clicking on the `Info` icon within the original video will show details
about the generated stream.
### What is a stream?
Stream is an encrypted HLS file with an `.m3u8` playlist that helps play a video
with support for seeking **without** downloading the full file.
Currently it converts videos into `720p` with `2mbps` bitrate in `H.264` format.
The generated stream is single blob (encrypted with AES) while the playlist file
(`.m3u8`) is another blob (encrypted using XChaCha20).
We cannot read the contents, duration or the number of chunks within the
generated stream.
### Will streams consume space in my storage?
While this feature is in beta, we will not count the storage consumed by your
streams against your storage quota. This may change in the future. If it does,
we will provide an option to opt-in to one of the following:
1. Original videos only
2. Compressed streams only
3. Both
### Something doesn't seem right, what to do?
As video streaming is still in beta, some things might not work correctly.
Please create a thread within the `#feedback` channel on
[Discord](https://discord.com/channels/948937918347608085/1121126215995113552)
or reach out to [support@ente.io](mailto:support@ente.io).

View File

@@ -43,10 +43,6 @@ need to disable this "Optimize battery usage" mode in the system settings for
Ente if you wish for Ente to automatically back up your photos in the
background.
On Android versions 15 and later, if an app is in private space and the private
space is locked, Android doesnt allow the app to run any background processes.
As a result, background sync will not work.
### Desktop
In addition to our mobile apps, the background sync also works on our desktop

View File

@@ -20,25 +20,23 @@ the logs just make the process a bit faster and easier.
- Select for the option to _Report a Bug_.
- Tap on _Report a bug_.
## Desktop and Web
## Desktop
- Click on _Help_ menu at the top of your screen, and select the _View logs_
option.
- Open settings (click on the three horizontal lines button located at the top
left corner of the screen).
- Click on the _Help_ option towards the bottom of settings.
- Click on _View logs_. This will show you the location of the logs on your
system (desktop), or download them from the browser onto your computer (web).
- Go back to settings.
- Click on _Support_. This will open your email client where you can attach the
logs in the email and describe the issue.
## Desktop
## Web
On the desktop app, you can also directly view the logs on your computer at the
following locations:
- macOS: `~/Library/Logs/ente/ente.log`
- Linux: `~/.config/ente/logs/ente.log`
- Windows: `%USERPROFILE%\AppData\Roaming\ente\logs\ente.log`
- Open settings (click on the three horizontal lines button located at the top
left corner of the screen).
- Click on _Debug Logs_ towards the bottom of settings.
- Click on _Download logs_
- Click on _Support_. This will open your email client where you can attach the
logs in the email and describe the issue.
## Send email manually

View File

@@ -22,10 +22,6 @@ interface Subscription {
originalTransactionID: string;
expiryTime: number;
userID: string;
attributes: {
customerID: string;
stripeAccountCountry: string;
};
}
interface UserDataResponse {
@@ -44,10 +40,6 @@ interface FormValues {
transactionId: string;
expiryTime: string | Date | null;
userId: string;
attributes: {
customerID: string;
stripeAccountCountry: string;
};
}
const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
@@ -61,10 +53,6 @@ const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
transactionId: "",
expiryTime: "",
userId: "",
attributes: {
"customerID": "",
"stripeAccountCountry": ""
},
});
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);
@@ -107,10 +95,6 @@ const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
"",
expiryTime: expiryTime,
userId: userDataResponse.subscription.userID || "",
attributes: {
customerID: userDataResponse.subscription.attributes.customerID || "",
stripeAccountCountry: userDataResponse.subscription.attributes.stripeAccountCountry || ""
}
});
} catch (error) {
console.error("Error fetching data:", error);
@@ -172,10 +156,6 @@ const UpdateSubscription: React.FC<UpdateSubscriptionProps> = ({
productId: values.productId,
paymentProvider: values.provider,
transactionId: values.transactionId,
attributes: {
customerID: values.attributes.customerID,
stripeAccountCountry: values.attributes.stripeAccountCountry
}
};
try {

View File

@@ -71,9 +71,9 @@ android {
dimension "default"
applicationIdSuffix ".dev"
}
face {
offline {
dimension "default"
applicationIdSuffix ".face"
applicationIdSuffix ".offline"
}
playstore {
dimension "default"

View File

@@ -2,5 +2,3 @@
# To ensure that stack traces is unambiguous
# https://developer.android.com/studio/build/shrink-code#decode-stack-trace
-keepattributes LineNumberTable,SourceFile
-keep class org.chromium.net.** { *; }

View File

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

View File

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

View File

@@ -6,9 +6,6 @@ PODS:
- connectivity_plus (0.0.1):
- Flutter
- FlutterMacOS
- cupertino_http (0.0.1):
- Flutter
- FlutterMacOS
- dart_ui_isolate (0.0.1):
- Flutter
- device_info_plus (0.0.1):
@@ -22,31 +19,31 @@ PODS:
- Flutter
- file_saver (0.0.1):
- Flutter
- Firebase/CoreOnly (11.2.0):
- FirebaseCore (= 11.2.0)
- Firebase/Messaging (11.2.0):
- Firebase/CoreOnly (11.6.0):
- FirebaseCore (~> 11.6.0)
- Firebase/Messaging (11.6.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.2.0)
- firebase_core (3.6.0):
- Firebase/CoreOnly (= 11.2.0)
- FirebaseMessaging (~> 11.6.0)
- firebase_core (3.10.0):
- Firebase/CoreOnly (= 11.6.0)
- Flutter
- firebase_messaging (15.1.3):
- Firebase/Messaging (= 11.2.0)
- firebase_messaging (15.2.0):
- Firebase/Messaging (= 11.6.0)
- firebase_core
- Flutter
- FirebaseCore (11.2.0):
- FirebaseCoreInternal (~> 11.0)
- FirebaseCore (11.6.0):
- FirebaseCoreInternal (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/Logger (~> 8.0)
- FirebaseCoreInternal (11.6.0):
- "GoogleUtilities/NSData+zlib (~> 8.0)"
- FirebaseInstallations (11.4.0):
- FirebaseCore (~> 11.0)
- FirebaseInstallations (11.6.0):
- FirebaseCore (~> 11.6.0)
- GoogleUtilities/Environment (~> 8.0)
- GoogleUtilities/UserDefaults (~> 8.0)
- PromisesObjC (~> 2.4)
- FirebaseMessaging (11.2.0):
- FirebaseCore (~> 11.0)
- FirebaseMessaging (11.6.0):
- FirebaseCore (~> 11.6.0)
- FirebaseInstallations (~> 11.0)
- GoogleDataTransport (~> 10.0)
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
@@ -71,7 +68,7 @@ PODS:
- OrderedSet (~> 6.0.3)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- flutter_native_splash (2.4.3):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
@@ -161,8 +158,6 @@ PODS:
- nanopb/encode (3.30910.0)
- native_video_player (1.0.0):
- Flutter
- objective_c (0.0.1):
- Flutter
- onnxruntime (0.0.1):
- Flutter
- onnxruntime-objc (= 1.18.0)
@@ -187,7 +182,7 @@ PODS:
- privacy_screen (0.0.1):
- Flutter
- PromisesObjC (2.4.0)
- receive_sharing_intent (1.8.0):
- receive_sharing_intent (1.8.1):
- Flutter
- screen_brightness_ios (0.1.0):
- Flutter
@@ -197,11 +192,11 @@ PODS:
- SDWebImageWebPCoder (0.14.6):
- libwebp (~> 1.0)
- SDWebImage/Core (~> 5.17)
- Sentry/HybridSDK (8.36.0)
- sentry_flutter (8.9.0):
- Sentry/HybridSDK (8.42.0)
- sentry_flutter (8.12.0):
- Flutter
- FlutterMacOS
- Sentry/HybridSDK (= 8.36.0)
- Sentry/HybridSDK (= 8.42.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
@@ -210,20 +205,21 @@ PODS:
- sqflite_darwin (0.0.4):
- Flutter
- FlutterMacOS
- "sqlite3 (3.46.1+1)":
- "sqlite3/common (= 3.46.1+1)"
- "sqlite3/common (3.46.1+1)"
- "sqlite3/dbstatvtab (3.46.1+1)":
- sqlite3 (3.47.2):
- sqlite3/common (= 3.47.2)
- sqlite3/common (3.47.2)
- sqlite3/dbstatvtab (3.47.2):
- sqlite3/common
- "sqlite3/fts5 (3.46.1+1)":
- sqlite3/fts5 (3.47.2):
- sqlite3/common
- "sqlite3/perf-threadsafe (3.46.1+1)":
- sqlite3/perf-threadsafe (3.47.2):
- sqlite3/common
- "sqlite3/rtree (3.46.1+1)":
- sqlite3/rtree (3.47.2):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- "sqlite3 (~> 3.46.0+1)"
- FlutterMacOS
- sqlite3 (~> 3.47.2)
- sqlite3/dbstatvtab
- sqlite3/fts5
- sqlite3/perf-threadsafe
@@ -231,7 +227,7 @@ PODS:
- system_info_plus (0.0.1):
- Flutter
- Toast (4.1.1)
- ua_client_hints (1.4.0):
- ua_client_hints (1.4.1):
- Flutter
- uni_links (0.0.1):
- Flutter
@@ -252,7 +248,6 @@ DEPENDENCIES:
- background_fetch (from `.symlinks/plugins/background_fetch/ios`)
- battery_info (from `.symlinks/plugins/battery_info/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
- dart_ui_isolate (from `.symlinks/plugins/dart_ui_isolate/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- ffmpeg_kit_flutter_full_gpl (from `.symlinks/plugins/ffmpeg_kit_flutter_full_gpl/ios`)
@@ -284,7 +279,6 @@ DEPENDENCIES:
- motionphoto (from `.symlinks/plugins/motionphoto/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- native_video_player (from `.symlinks/plugins/native_video_player/ios`)
- objective_c (from `.symlinks/plugins/objective_c/ios`)
- onnxruntime (from `.symlinks/plugins/onnxruntime/ios`)
- open_mail_app (from `.symlinks/plugins/open_mail_app/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
@@ -298,7 +292,7 @@ DEPENDENCIES:
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
- system_info_plus (from `.symlinks/plugins/system_info_plus/ios`)
- ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`)
@@ -338,8 +332,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/battery_info/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/darwin"
cupertino_http:
:path: ".symlinks/plugins/cupertino_http/darwin"
dart_ui_isolate:
:path: ".symlinks/plugins/dart_ui_isolate/ios"
device_info_plus:
@@ -402,8 +394,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/move_to_background/ios"
native_video_player:
:path: ".symlinks/plugins/native_video_player/ios"
objective_c:
:path: ".symlinks/plugins/objective_c/ios"
onnxruntime:
:path: ".symlinks/plugins/onnxruntime/ios"
open_mail_app:
@@ -431,7 +421,7 @@ EXTERNAL SOURCES:
sqflite_darwin:
:path: ".symlinks/plugins/sqflite_darwin/darwin"
sqlite3_flutter_libs:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
system_info_plus:
:path: ".symlinks/plugins/system_info_plus/ios"
ua_client_hints:
@@ -452,26 +442,25 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57
battery_info: a06b00c06a39bc94c92beebf600f1810cb6c8c87
connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
connectivity_plus: 2256d3e20624a7749ed21653aafe291a46446fee
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b
ffmpeg_kit_flutter_full_gpl: ce18b888487c05c46ed252cd2e7956812f2e3bd1
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
firebase_core: 085320ddfaacb80d1a96eac3a87857afcc150db1
firebase_messaging: d398edc15fe825f832836e74f6ac61e8cd2f3ad3
FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
Firebase: 374a441a91ead896215703a674d58cdb3e9d772b
firebase_core: 2337982fb78ee4d8d91e608b0a3d4f44346a93c8
firebase_messaging: f3bddfa28c2cad70b3341bf461e987a24efd28d6
FirebaseCore: 48b0dd707581cf9c1a1220da68223fb0a562afaa
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152
FirebaseInstallations: efc0946fc756e4d22d8113f7c761948120322e8c
FirebaseMessaging: e1aca1fcc23e8b9eddb0e33f375ff90944623021
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: cd533cdc7ea5eda6fabb2c7f78521c71207778a4
flutter_image_compress: 4b058288a81f76e5e80340af37c709abafff34c4
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0
flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
flutter_sodium: 152647449ba89a157fd48d7e293dcd6d29c6ab0e
fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
@@ -496,38 +485,37 @@ SPEC CHECKSUMS:
move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
open_mail_app: 06d5a4162866388a92b1df3deb96e56be20cf45c
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
package_info_plus: 566e1b7a2f3900e4b0020914ad3fc051dcc95596
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
receive_sharing_intent: f6a12b7e8f7ed745f61c982de8a65de88db44a44
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
sentry_flutter: 0a211008f52553ba5dd81ceb71f48d78f0f1f6ab
share_plus: 011d6fb4f9d2576b83179a3a5c5e323202cdabcf
Sentry: 38ed8bf38eab5812787274bf591e528074c19e02
sentry_flutter: a72ca0eb6e78335db7c4ddcddd1b9f6c8ed5b764
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite_darwin: 44bb54cc302bff1fbe5752293aba1820b157cf1c
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
sqlite3_flutter_libs: 9379996d65aa23dcda7585a5b58766cebe0aa042
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71
sqlite3_flutter_libs: 5235ce0546528db87927a3ef1baff8b7d5107f0e
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
ua_client_hints: 0b48eae1134283f5b131ee0871fa878377f07a01
ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586
uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
volume_controller: ca1cde542ee70fad77d388f82e9616488110942b
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
wakelock_plus: 8c239121a007daa1d6759c6acdc507860273dd2f
PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd

View File

@@ -292,7 +292,6 @@
"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
"${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework",
"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
"${BUILT_PRODUCTS_DIR}/cupertino_http/cupertino_http.framework",
"${BUILT_PRODUCTS_DIR}/dart_ui_isolate/dart_ui_isolate.framework",
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework",
@@ -322,7 +321,6 @@
"${BUILT_PRODUCTS_DIR}/move_to_background/move_to_background.framework",
"${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
"${BUILT_PRODUCTS_DIR}/native_video_player/native_video_player.framework",
"${BUILT_PRODUCTS_DIR}/objective_c/objective_c.framework",
"${BUILT_PRODUCTS_DIR}/open_mail_app/open_mail_app.framework",
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
"${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
@@ -389,7 +387,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/cupertino_http.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/dart_ui_isolate.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework",
@@ -419,7 +416,6 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/move_to_background.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/native_video_player.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/objective_c.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_mail_app.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",

View File

@@ -18,7 +18,6 @@ import 'package:photos/core/error-reporting/tunneled_transport.dart';
import "package:photos/core/errors.dart";
import 'package:photos/models/typedefs.dart';
import "package:photos/utils/device_info.dart";
import "package:photos/utils/ram_check_util.dart";
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -206,12 +205,6 @@ class SuperLogging {
}),
);
unawaited(
checkDeviceTotalRAM().then((ram) {
if (ram != null) $.info("Device RAM: ${ram}MB");
}),
);
if (appConfig.body == null) return;
if (enable && sentryIsEnabled) {
@@ -243,7 +236,7 @@ class SuperLogging {
}
static _shouldSkipSentry(Object error) {
if (error is DioException) {
if (error is DioError) {
return true;
}
final bool result = error is StorageLimitExceededError ||

View File

@@ -1,7 +1,6 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:package_info_plus/package_info_plus.dart';
import "package:photos/core/configuration.dart";
import "package:photos/core/event_bus.dart";
@@ -9,17 +8,18 @@ import 'package:photos/core/network/ente_interceptor.dart';
import "package:photos/events/endpoint_updated_event.dart";
import "package:ua_client_hints/ua_client_hints.dart";
int kConnectTimeout = 15000;
class NetworkClient {
late Dio _dio;
late Dio _enteDio;
static const kConnectTimeout = 15;
Future<void> init(PackageInfo packageInfo) async {
final String ua = await userAgent();
final endpoint = Configuration.instance.getHttpEndpoint();
_dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: kConnectTimeout),
connectTimeout: kConnectTimeout,
headers: {
HttpHeaders.userAgentHeader: ua,
'X-Client-Version': packageInfo.version,
@@ -30,7 +30,7 @@ class NetworkClient {
_enteDio = Dio(
BaseOptions(
baseUrl: endpoint,
connectTimeout: const Duration(seconds: kConnectTimeout),
connectTimeout: kConnectTimeout,
headers: {
HttpHeaders.userAgentHeader: ua,
'X-Client-Version': packageInfo.version,
@@ -38,10 +38,6 @@ class NetworkClient {
},
),
);
_dio.httpClientAdapter = NativeAdapter();
_enteDio.httpClientAdapter = NativeAdapter();
_setupInterceptors(endpoint);
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {

View File

@@ -251,20 +251,20 @@ class CollectionsDB {
Map<String, dynamic> _getRowForCollection(Collection collection) {
final row = <String, dynamic>{};
row[columnID] = collection.id;
row[columnOwner] = collection.owner.toJson();
row[columnOwner] = collection.owner!.toJson();
row[columnEncryptedKey] = collection.encryptedKey;
row[columnKeyDecryptionNonce] = collection.keyDecryptionNonce;
row[columnName] = collection.name;
row[columnEncryptedName] = collection.encryptedName;
row[columnNameDecryptionNonce] = collection.nameDecryptionNonce;
row[columnType] = typeToString(collection.type);
row[columnType] = Collection.typeToString(collection.type);
row[columnEncryptedPath] = collection.attributes.encryptedPath;
row[columnPathDecryptionNonce] = collection.attributes.pathDecryptionNonce;
row[columnVersion] = collection.attributes.version;
row[columnSharees] =
json.encode(collection.sharees.map((x) => x.toMap()).toList());
json.encode(collection.sharees?.map((x) => x?.toMap()).toList());
row[columnPublicURLs] =
json.encode(collection.publicURLs.map((x) => x.toMap()).toList());
json.encode(collection.publicURLs?.map((x) => x?.toMap()).toList());
row[columnUpdationTime] = collection.updationTime;
if (collection.isDeleted) {
row[columnIsDeleted] = _sqlBoolTrue;
@@ -290,7 +290,7 @@ class CollectionsDB {
row[columnName],
row[columnEncryptedName],
row[columnNameDecryptionNonce],
typeFromString(row[columnType]),
Collection.typeFromString(row[columnType]),
CollectionAttributes(
encryptedPath: row[columnEncryptedPath],
pathDecryptionNonce: row[columnPathDecryptionNonce],

View File

@@ -1733,17 +1733,11 @@ class FilesDB {
Future<List<EnteFile>> getAllFilesAfterDate({
required FileType fileType,
required DateTime beginDate,
required int userID,
}) async {
final db = await instance.sqliteAsyncDB;
final results = await db.getAll(
'''
SELECT * FROM $filesTable
WHERE $columnFileType = ?
AND $columnCreationTime > ?
AND $columnUploadedFileID != -1
AND $columnOwnerID = $userID
ORDER BY $columnCreationTime DESC
SELECT * FROM $filesTable WHERE $columnFileType = ? AND $columnCreationTime > ? AND $columnUploadedFileID != -1
''',
[getInt(fileType), beginDate.microsecondsSinceEpoch],
);

View File

@@ -322,20 +322,22 @@ class _AddContactPage extends State<AddContactPage> {
final int ownerID = Configuration.instance.getUserID()!;
existingEmails.add(Configuration.instance.getEmail()!);
for (final c in CollectionsService.instance.getActiveCollections()) {
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null &&
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
!existingEmails.contains(u.email)) {
existingEmails.add(u.email);
suggestedUsers.add(u);
}
}
} else if (c.owner.id != null &&
c.owner.email.isNotEmpty &&
!existingEmails.contains(c.owner.email)) {
existingEmails.add(c.owner.email);
suggestedUsers.add(c.owner);
} else if (c.owner != null &&
c.owner!.id != null &&
c.owner!.email.isNotEmpty &&
!existingEmails.contains(c.owner!.email)) {
existingEmails.add(c.owner!.email);
suggestedUsers.add(c.owner!);
}
}
final cachedUserDetails = UserService.instance.getCachedUserDetails();

View File

@@ -12,7 +12,7 @@ class CastGateway {
);
return response.data["publicKey"];
} catch (e) {
if (e is DioException && e.response != null) {
if (e is DioError && e.response != null) {
if (e.response!.statusCode == 404) {
return null;
} else if (e.response!.statusCode == 403) {

View File

@@ -32,7 +32,7 @@ class EntityGateway {
},
);
return EntityKey.fromMap(response.data);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
throw EntityKeyNotFound();
} else {

View File

@@ -894,7 +894,6 @@ class MessageLookup extends MessageLookupByLibrary {
"faceRecognition":
MessageLookupByLibrary.simpleMessage("Face recognition"),
"faces": MessageLookupByLibrary.simpleMessage("Faces"),
"failed": MessageLookupByLibrary.simpleMessage("Failed"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Failed to apply code"),
"failedToCancel":
@@ -1031,7 +1030,6 @@ class MessageLookup extends MessageLookupByLibrary {
"indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
"Indexing is paused. It will automatically resume when device is ready."),
"ineligible": MessageLookupByLibrary.simpleMessage("Ineligible"),
"info": MessageLookupByLibrary.simpleMessage("Info"),
"insecureDevice":
MessageLookupByLibrary.simpleMessage("Insecure device"),
@@ -1063,8 +1061,6 @@ class MessageLookup extends MessageLookupByLibrary {
"Selected items will be removed from this album"),
"join": MessageLookupByLibrary.simpleMessage("Join"),
"joinAlbum": MessageLookupByLibrary.simpleMessage("Join album"),
"joinAlbumConfirmationDialogBody": MessageLookupByLibrary.simpleMessage(
"Joining an album will make your email visible to its participants."),
"joinAlbumSubtext":
MessageLookupByLibrary.simpleMessage("to view and add your photos"),
"joinAlbumSubtextViewer": MessageLookupByLibrary.simpleMessage(
@@ -1367,9 +1363,7 @@ class MessageLookup extends MessageLookupByLibrary {
"pinAlbum": MessageLookupByLibrary.simpleMessage("Pin album"),
"pinLock": MessageLookupByLibrary.simpleMessage("PIN lock"),
"playOnTv": MessageLookupByLibrary.simpleMessage("Play album on TV"),
"playOriginal": MessageLookupByLibrary.simpleMessage("Play original"),
"playStoreFreeTrialValidTill": m52,
"playStream": MessageLookupByLibrary.simpleMessage("Play stream"),
"playstoreSubscription":
MessageLookupByLibrary.simpleMessage("PlayStore subscription"),
"pleaseCheckYourInternetConnectionAndTryAgain":
@@ -1416,15 +1410,13 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Private sharing"),
"proceed": MessageLookupByLibrary.simpleMessage("Proceed"),
"processed": MessageLookupByLibrary.simpleMessage("Processed"),
"processing": MessageLookupByLibrary.simpleMessage("Processing"),
"processingImport": m55,
"processingVideos":
MessageLookupByLibrary.simpleMessage("Processing videos"),
MessageLookupByLibrary.simpleMessage("Processing Videos"),
"publicLinkCreated":
MessageLookupByLibrary.simpleMessage("Public link created"),
"publicLinkEnabled":
MessageLookupByLibrary.simpleMessage("Public link enabled"),
"queued": MessageLookupByLibrary.simpleMessage("Queued"),
"quickLinks": MessageLookupByLibrary.simpleMessage("Quick links"),
"radius": MessageLookupByLibrary.simpleMessage("Radius"),
"raiseTicket": MessageLookupByLibrary.simpleMessage("Raise ticket"),
@@ -1940,7 +1932,7 @@ class MessageLookup extends MessageLookupByLibrary {
"videoInfo": MessageLookupByLibrary.simpleMessage("Video Info"),
"videoSmallCase": MessageLookupByLibrary.simpleMessage("video"),
"videoStreaming":
MessageLookupByLibrary.simpleMessage("Video streaming"),
MessageLookupByLibrary.simpleMessage("Video Streaming"),
"videos": MessageLookupByLibrary.simpleMessage("Videos"),
"viewActiveSessions":
MessageLookupByLibrary.simpleMessage("View active sessions"),

View File

@@ -11131,20 +11131,20 @@ class S {
);
}
/// `Video streaming`
/// `Video Streaming`
String get videoStreaming {
return Intl.message(
'Video streaming',
'Video Streaming',
name: 'videoStreaming',
desc: '',
args: [],
);
}
/// `Processing videos`
/// `Processing Videos`
String get processingVideos {
return Intl.message(
'Processing videos',
'Processing Videos',
name: 'processingVideos',
desc: '',
args: [],
@@ -11160,76 +11160,6 @@ class S {
args: [],
);
}
/// `Processing`
String get processing {
return Intl.message(
'Processing',
name: 'processing',
desc: '',
args: [],
);
}
/// `Queued`
String get queued {
return Intl.message(
'Queued',
name: 'queued',
desc: '',
args: [],
);
}
/// `Ineligible`
String get ineligible {
return Intl.message(
'Ineligible',
name: 'ineligible',
desc: '',
args: [],
);
}
/// `Failed`
String get failed {
return Intl.message(
'Failed',
name: 'failed',
desc: '',
args: [],
);
}
/// `Play stream`
String get playStream {
return Intl.message(
'Play stream',
name: 'playStream',
desc: '',
args: [],
);
}
/// `Play original`
String get playOriginal {
return Intl.message(
'Play original',
name: 'playOriginal',
desc: '',
args: [],
);
}
/// `Joining an album will make your email visible to its participants.`
String get joinAlbumConfirmationDialogBody {
return Intl.message(
'Joining an album will make your email visible to its participants.',
name: 'joinAlbumConfirmationDialogBody',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View File

@@ -1665,14 +1665,7 @@
"@linkPersonCaption": {
"description": "Caption for the 'Link person' title. It should be a continuation of the 'Link person' title. Just like how 'Link person' + 'for better sharing experience' forms a proper sentence in English, the combination of these two strings should also be a proper sentence in other languages."
},
"videoStreaming": "Video streaming",
"processingVideos": "Processing videos",
"streamDetails": "Stream details",
"processing": "Processing",
"queued": "Queued",
"ineligible": "Ineligible",
"failed": "Failed",
"playStream": "Play stream",
"playOriginal": "Play original",
"joinAlbumConfirmationDialogBody" : "Joining an album will make your email visible to its participants."
"videoStreaming": "Video Streaming",
"processingVideos": "Processing Videos",
"streamDetails": "Stream details"
}

View File

@@ -45,7 +45,7 @@ class CreateRequest {
map['keyDecryptionNonce'] = keyDecryptionNonce;
map['encryptedName'] = encryptedName;
map['nameDecryptionNonce'] = nameDecryptionNonce;
map['type'] = typeToString(type);
map['type'] = Collection.typeToString(type);
if (attributes != null) {
map['attributes'] = attributes!.toMap();
}

View File

@@ -1,51 +0,0 @@
import "package:photos/models/file/file.dart";
import "package:photos/models/location/location.dart";
class BaseLocation {
final List<EnteFile> files;
int? firstCreationTime;
int? lastCreationTime;
final Location location;
final bool isCurrentBase;
BaseLocation(
this.files,
this.location,
this.isCurrentBase, {
this.firstCreationTime,
this.lastCreationTime,
});
int averageCreationTime() {
if (firstCreationTime != null && lastCreationTime != null) {
return (firstCreationTime! + lastCreationTime!) ~/ 2;
}
final List<int> creationTimes = files
.where((file) => file.creationTime != null)
.map((file) => file.creationTime!)
.toList();
if (creationTimes.length < 2) {
return creationTimes.isEmpty ? 0 : creationTimes.first;
}
creationTimes.sort();
firstCreationTime ??= creationTimes.first;
lastCreationTime ??= creationTimes.last;
return (firstCreationTime! + lastCreationTime!) ~/ 2;
}
BaseLocation copyWith({
List<EnteFile>? files,
int? firstCreationTime,
int? lastCreationTime,
Location? location,
bool? isCurrentBase,
}) {
return BaseLocation(
files ?? this.files,
location ?? this.location,
isCurrentBase ?? this.isCurrentBase,
firstCreationTime: firstCreationTime ?? this.firstCreationTime,
lastCreationTime: lastCreationTime ?? this.lastCreationTime,
);
}
}

View File

@@ -8,7 +8,7 @@ import "package:photos/models/metadata/common_keys.dart";
class Collection {
final int id;
final User owner;
final User? owner;
final String encryptedKey;
final String? keyDecryptionNonce;
@Deprecated("Use collectionName instead")
@@ -20,8 +20,8 @@ class Collection {
final String? nameDecryptionNonce;
final CollectionType type;
final CollectionAttributes attributes;
final List<User> sharees;
final List<PublicURL> publicURLs;
final List<User?>? sharees;
final List<PublicURL?>? publicURLs;
final int updationTime;
final bool isDeleted;
@@ -95,12 +95,12 @@ class Collection {
// hasLink returns true if there's any link attached to the collection
// including expired links
bool get hasLink => publicURLs.isNotEmpty;
bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
// hasSharees returns true if the collection is shared with other ente users
bool get hasSharees => sharees.isNotEmpty;
bool get hasSharees => sharees != null && sharees!.isNotEmpty;
bool get isPinned => (magicMetadata.order ?? 0) != 0;
@@ -121,43 +121,52 @@ class Collection {
}
List<User> getSharees() {
return sharees;
final List<User> result = [];
if (sharees == null) {
return result;
}
for (final User? u in sharees!) {
if (u != null) {
result.add(u);
}
}
return result;
}
bool isOwner(int userID) {
return (owner.id ?? -100) == userID;
return (owner?.id ?? 0) == userID;
}
bool isDownloadEnabledForPublicLink() {
if (publicURLs.isEmpty) {
if (publicURLs == null || publicURLs!.isEmpty) {
return false;
}
return publicURLs.first.enableDownload;
return publicURLs?.first?.enableDownload ?? true;
}
bool isCollectEnabledForPublicLink() {
if (publicURLs.isEmpty) {
if (publicURLs == null || publicURLs!.isEmpty) {
return false;
}
return publicURLs.first.enableCollect;
return publicURLs?.first?.enableCollect ?? false;
}
bool get isJoinEnabled {
if (publicURLs.isEmpty) {
if (publicURLs == null || publicURLs!.isEmpty) {
return false;
}
return publicURLs.first.enableJoin;
return publicURLs?.first?.enableJoin ?? false;
}
CollectionParticipantRole getRole(int userID) {
if (isOwner(userID)) {
return CollectionParticipantRole.owner;
}
if (sharees.isEmpty) {
if (sharees == null) {
return CollectionParticipantRole.unknown;
}
for (final User u in sharees) {
if (u.id == userID) {
for (final User? u in sharees!) {
if (u != null && u.id == userID) {
if (u.isViewer) {
return CollectionParticipantRole.viewer;
} else if (u.isCollaborator) {
@@ -176,8 +185,40 @@ class Collection {
}
void updateSharees(List<User> newSharees) {
sharees.clear();
sharees.addAll(newSharees);
sharees?.clear();
sharees?.addAll(newSharees);
}
static CollectionType typeFromString(String type) {
switch (type) {
case "folder":
return CollectionType.folder;
case "favorites":
return CollectionType.favorites;
case "uncategorized":
return CollectionType.uncategorized;
case "album":
return CollectionType.album;
case "unknown":
return CollectionType.unknown;
}
debugPrint("unexpected collection type $type");
return CollectionType.unknown;
}
static String typeToString(CollectionType type) {
switch (type) {
case CollectionType.folder:
return "folder";
case CollectionType.favorites:
return "favorites";
case CollectionType.album:
return "album";
case CollectionType.uncategorized:
return "uncategorized";
case CollectionType.unknown:
return "unknown";
}
}
Collection copyWith({
@@ -262,38 +303,6 @@ enum CollectionType {
unknown,
}
CollectionType typeFromString(String type) {
switch (type) {
case "folder":
return CollectionType.folder;
case "favorites":
return CollectionType.favorites;
case "uncategorized":
return CollectionType.uncategorized;
case "album":
return CollectionType.album;
case "unknown":
return CollectionType.unknown;
}
debugPrint("unexpected collection type $type");
return CollectionType.unknown;
}
String typeToString(CollectionType type) {
switch (type) {
case CollectionType.folder:
return "folder";
case CollectionType.favorites:
return "favorites";
case CollectionType.album:
return "album";
case CollectionType.uncategorized:
return "uncategorized";
case CollectionType.unknown:
return "unknown";
}
}
extension CollectionTypeExtn on CollectionType {
bool get canDelete =>
this != CollectionType.favorites && this != CollectionType.uncategorized;

View File

@@ -160,7 +160,6 @@ class EnteFile {
Future<Map<String, dynamic>> getMetadataForUpload(
MediaUploadData mediaUploadData,
ParsedExifDateTime? exifTime,
) async {
final asset = await getAsset;
// asset can be null for files shared to app
@@ -171,24 +170,36 @@ class EnteFile {
}
}
bool hasExifTime = false;
if (exifTime != null && exifTime.time != null) {
hasExifTime = true;
creationTime = exifTime.time!.microsecondsSinceEpoch;
}
if (mediaUploadData.exifData != null) {
mediaUploadData.isPanorama =
checkPanoramaFromEXIF(null, mediaUploadData.exifData);
}
if (mediaUploadData.isPanorama != true &&
fileType == FileType.image &&
if ((fileType == FileType.image || fileType == FileType.video) &&
mediaUploadData.sourceFile != null) {
try {
final xmpData = await getXmp(mediaUploadData.sourceFile!);
mediaUploadData.isPanorama = checkPanoramaFromXMP(xmpData);
} catch (_) {}
mediaUploadData.isPanorama ??= false;
}
final exifData = await getExifFromSourceFile(mediaUploadData.sourceFile!);
if (exifData != null) {
if (fileType == FileType.image) {
final exifTime = await getCreationTimeFromEXIF(null, exifData);
if (exifTime != null) {
hasExifTime = true;
creationTime = exifTime.microsecondsSinceEpoch;
}
mediaUploadData.isPanorama = checkPanoramaFromEXIF(null, exifData);
if (mediaUploadData.isPanorama != true) {
try {
final xmpData = await getXmp(mediaUploadData.sourceFile!);
mediaUploadData.isPanorama = checkPanoramaFromXMP(xmpData);
} catch (_) {}
mediaUploadData.isPanorama ??= false;
}
}
if (Platform.isAndroid) {
//Fix for missing location data in lower android versions.
final Location? exifLocation = locationFromExif(exifData);
if (Location.isValidLocation(exifLocation)) {
location = exifLocation;
}
}
}
}
// Try to get the timestamp from fileName. In case of iOS, file names are
// generic IMG_XXXX, so only parse it on Android devices
if (!hasExifTime && Platform.isAndroid && title != null) {

View File

@@ -14,8 +14,6 @@ const latKey = "lat";
const longKey = "long";
const motionVideoIndexKey = "mvi";
const noThumbKey = "noThumb";
const dateTimeKey = 'dateTime';
const offsetTimeKey = 'offsetTime';
class MagicMetadata {
// 0 -> visible
@@ -48,11 +46,6 @@ class PubMagicMetadata {
double? lat;
double? long;
// ISO 8601 datetime without timezone. This contains the date and time of the photo in the original tz
// where the photo was taken.
String? dateTime;
String? offsetTime;
// Motion Video Index. Positive value (>0) indicates that the file is a motion
// photo
int? mvi;
@@ -81,8 +74,6 @@ class PubMagicMetadata {
this.mvi,
this.noThumb,
this.mediaType,
this.dateTime,
this.offsetTime,
});
factory PubMagicMetadata.fromEncodedJson(String encodedJson) =>
@@ -105,8 +96,6 @@ class PubMagicMetadata {
mvi: map[motionVideoIndexKey],
noThumb: map[noThumbKey],
mediaType: map[mediaTypeKey],
dateTime: map[dateTimeKey],
offsetTime: map[offsetTimeKey],
);
}

View File

@@ -1,32 +0,0 @@
import "package:photos/models/file/file.dart";
import "package:photos/models/location/location.dart";
class TripMemory {
final List<EnteFile> files;
final Location location;
final int firstCreationTime;
final int lastCreationTime;
TripMemory(
this.files,
this.location,
this.firstCreationTime,
this.lastCreationTime,
);
int get averageCreationTime => (firstCreationTime + lastCreationTime) ~/ 2;
TripMemory copyWith({
List<EnteFile>? files,
Location? location,
int? firstCreationTime,
int? lastCreationTime,
}) {
return TripMemory(
files ?? this.files,
location ?? this.location,
firstCreationTime ?? this.firstCreationTime,
lastCreationTime ?? this.lastCreationTime,
);
}
}

View File

@@ -132,7 +132,7 @@ class MultiPartUploader {
// upload individual parts and get their etags
try {
etags = await _uploadParts(multipartInfo, encryptedFile);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response?.statusCode == 404) {
_logger.severe(
"Multipart upload not found for key ${multipartInfo.urls.objectKey}",
@@ -157,7 +157,7 @@ class MultiPartUploader {
etags,
multipartInfo.urls.completeURL,
);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response?.statusCode == 404) {
_logger.severe(
"Multipart upload not found for key ${multipartInfo.urls.objectKey}",

View File

@@ -92,7 +92,7 @@ class BillingService {
},
);
return Subscription.fromMap(response.data["subscription"]);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response!.statusCode == 409) {
throw SubscriptionAlreadyClaimedError();
} else {
@@ -109,7 +109,7 @@ class BillingService {
final response = await _enteDio.get("/billing/subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioException catch (e, s) {
} on DioError catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -121,7 +121,7 @@ class BillingService {
await _enteDio.post("/billing/stripe/cancel-subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioException catch (e, s) {
} on DioError catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -133,7 +133,7 @@ class BillingService {
await _enteDio.post("/billing/stripe/activate-subscription");
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
} on DioException catch (e, s) {
} on DioError catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -150,7 +150,7 @@ class BillingService {
},
);
return response.data["url"];
} on DioException catch (e, s) {
} on DioError catch (e, s) {
_logger.severe(e, s);
rethrow;
}

View File

@@ -139,7 +139,7 @@ class CollectionsService {
}
}
// remove reference for incoming collections when unshared/deleted
if (collection.isDeleted && ownerID != collection.owner.id) {
if (collection.isDeleted && ownerID != collection.owner?.id) {
await _db.deleteCollection(collection.id);
} else {
// keep entry for deletedCollection as collectionKey may be used during
@@ -394,7 +394,7 @@ class CollectionsService {
final List<Collection> collections =
getCollectionsForUI(includedShared: true);
for (final c in collections) {
if (c.owner.id == Configuration.instance.getUserID()) {
if (c.owner!.id == Configuration.instance.getUserID()) {
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
outgoing.add(c);
} else if (c.isQuickLinkCollection()) {
@@ -472,8 +472,8 @@ class CollectionsService {
if (collectionID != null) {
final Collection? collection = getCollectionByID(collectionID);
if (collection != null) {
if (collection.owner.id == userID) {
_cachedUserIdToUser[userID] = collection.owner;
if (collection.owner?.id == userID) {
_cachedUserIdToUser[userID] = collection.owner!;
} else {
final matchingUser = collection.getSharees().firstWhereOrNull(
(u) => u.id == userID,
@@ -553,7 +553,7 @@ class CollectionsService {
unawaited(_db.insert([_collectionIDToCollections[collectionID]!]));
RemoteSyncService.instance.sync(silently: true).ignore();
return sharees;
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response?.statusCode == 402) {
throw SharingNotPermittedForFreeAccountsError();
}
@@ -641,7 +641,7 @@ class CollectionsService {
} else {
await _handleCollectionDeletion(collection);
}
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null) {
debugPrint("Error " + e.response!.toString());
}
@@ -698,7 +698,7 @@ class CollectionsService {
);
final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey);
Uint8List? collectionKey;
if (collection.owner.id == _config.getUserID()) {
if (collection.owner?.id == _config.getUserID()) {
// If the collection is owned by the user, decrypt with the master key
if (_config.getKey() == null) {
// Possible during AppStore account migration, where SecureStorage
@@ -767,7 +767,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner.id != ownerID) {
if (collection.owner?.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -798,7 +798,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/magic-metadata",
data: params.toJson(),
data: params,
);
// update the local information so that it's reflected on UI
collection.mMdEncodedJson = jsonEncode(jsonToUpdate);
@@ -808,7 +808,7 @@ class CollectionsService {
// trigger sync to fetch the latest collection state from server
sync().ignore();
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response?.statusCode == 409) {
_logger.severe('collection magic data out of sync');
sync().ignore();
@@ -826,7 +826,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner.id != ownerID) {
if (collection.owner?.id != ownerID) {
throw AssertionError("cannot modify albums not owned by you");
}
// read the existing magic metadata and apply new updates to existing data
@@ -857,7 +857,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/public-magic-metadata",
data: params.toJson(),
data: params,
);
// update the local information so that it's reflected on UI
collection.mMdPubEncodedJson = jsonEncode(jsonToUpdate);
@@ -867,7 +867,7 @@ class CollectionsService {
_cacheLocalPathAndCollection(collection);
// trigger sync to fetch the latest collection state from server
sync().ignore();
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response?.statusCode == 409) {
_logger.severe('collection magic data out of sync');
sync().ignore();
@@ -885,7 +885,7 @@ class CollectionsService {
) async {
final int ownerID = Configuration.instance.getUserID()!;
try {
if (collection.owner.id == ownerID) {
if (collection.owner?.id == ownerID) {
throw AssertionError("cannot modify sharee settings for albums owned "
"by you");
}
@@ -917,7 +917,7 @@ class CollectionsService {
);
await _enteDio.put(
"/collections/sharee-magic-metadata",
data: params.toJson(),
data: params,
);
// update the local information so that it's reflected on UI
collection.sharedMmdJson = jsonEncode(jsonToUpdate);
@@ -927,7 +927,7 @@ class CollectionsService {
_cacheLocalPathAndCollection(collection);
// trigger sync to fetch the latest collection state from server
sync().ignore();
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response?.statusCode == 409) {
_logger.severe('collection magic data out of sync');
sync().ignore();
@@ -952,13 +952,13 @@ class CollectionsService {
"enableJoin": true,
},
);
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
CollectionUpdatedEvent(collection.id, <EnteFile>[], "shareUrL"),
);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response?.statusCode == 402) {
throw SharingNotPermittedForFreeAccountsError();
}
@@ -980,14 +980,14 @@ class CollectionsService {
data: json.encode(prop),
);
// remove existing url information
collection.publicURLs.clear();
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
collection.publicURLs?.clear();
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
CollectionUpdatedEvent(collection.id, <EnteFile>[], "updateUrl"),
);
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response?.statusCode == 402) {
throw SharingNotPermittedForFreeAccountsError();
}
@@ -1003,7 +1003,7 @@ class CollectionsService {
await _enteDio.delete(
"/collections/share-url/" + collection.id.toString(),
);
collection.publicURLs.clear();
collection.publicURLs?.clear();
await _db.insert(List.from([collection]));
_collectionIDToCollections[collection.id] = collection;
Bus.instance.fire(
@@ -1013,7 +1013,7 @@ class CollectionsService {
"disableShareUrl",
),
);
} on DioException catch (e) {
} on DioError catch (e) {
_logger.info(e);
rethrow;
}
@@ -1038,7 +1038,7 @@ class CollectionsService {
return collections;
} catch (e, s) {
_logger.warning(e, s);
if (e is DioException && e.response?.statusCode == 401) {
if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError();
}
rethrow;
@@ -1091,7 +1091,7 @@ class CollectionsService {
} catch (e, s) {
_logger.warning(e, s);
_logger.severe("Failed to fetch public collection");
if (e is DioException && e.response?.statusCode == 410) {
if (e is DioError && e.response?.statusCode == 410) {
await showInfoDialog(
context,
title: S.of(context).linkExpired,
@@ -1100,7 +1100,7 @@ class CollectionsService {
throw UnauthorizedError();
}
await showGenericErrorDialog(context: context, error: e);
if (e is DioException && e.response?.statusCode == 401) {
if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError();
}
rethrow;
@@ -1300,7 +1300,7 @@ class CollectionsService {
_cacheLocalPathAndCollection(collection);
return collection;
} catch (e) {
if (e is DioException && e.response?.statusCode == 401) {
if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError();
}
_logger.severe('failed to fetch collection: $collectionID', e);

View File

@@ -230,7 +230,7 @@ class FavoritesService {
if (_cachedFavoritesCollectionID == null) {
final collections = _collectionsService.getActiveCollections();
for (final collection in collections) {
if (collection.owner.id == _config.getUserID() &&
if (collection.owner!.id == _config.getUserID() &&
collection.type == CollectionType.favorites) {
_cachedFavoritesCollectionID = collection.id;
return collection;

View File

@@ -117,7 +117,7 @@ class FileMagicService {
// should be eventually synced after remote sync has completed
await _filesDB.insertMultiple(files);
RemoteSyncService.instance.sync(silently: true).ignore();
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response!.statusCode == 409) {
RemoteSyncService.instance.sync(silently: true).ignore();
}
@@ -185,7 +185,7 @@ class FileMagicService {
// update the state of the selected file. Same file in other collection
// should be eventually synced after remote sync has completed
RemoteSyncService.instance.sync(silently: true).ignore();
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response!.statusCode == 409) {
RemoteSyncService.instance.sync(silently: true).ignore();
}

View File

@@ -32,7 +32,7 @@ extension HiddenService on CollectionsService {
final int userID = config.getUserID()!;
final allDefaultHidden = collectionIDToCollections.values
.where(
(element) => element.isDefaultHidden() && element.owner.id == userID,
(element) => element.isDefaultHidden() && element.owner!.id == userID,
)
.toList();
@@ -101,7 +101,7 @@ extension HiddenService on CollectionsService {
collectionIDToCollections.values.firstWhereOrNull(
(element) =>
element.type == CollectionType.uncategorized &&
element.owner.id == userID,
element.owner!.id == userID,
);
if (matchedCollection != null) {
cachedUncategorizedCollection = matchedCollection;
@@ -166,9 +166,7 @@ extension HiddenService on CollectionsService {
await dialog.hide();
} on AssertionError catch (e) {
await dialog.hide();
unawaited(
showErrorDialog(context, S.of(context).oops, e.message as String),
);
unawaited(showErrorDialog(context, S.of(context).oops, e.message as String));
return false;
} catch (e, s) {
_logger.severe("Could not hide", e, s);

View File

@@ -71,12 +71,6 @@ class HomeWidgetService {
element.fileType == FileType.image,
);
if (files.isEmpty) {
await clearHomeWidget();
_logger.info("No images found");
return;
}
final randomNumber = Random().nextInt(files.length);
final randomFile = files.elementAt(randomNumber);
final fullImage = await getFileFromServer(randomFile);

View File

@@ -78,8 +78,8 @@ class LocalSyncService {
return _existingSync!.future;
}
_existingSync = Completer<void>();
final int ownerID = Configuration.instance.getUserID()!;
final int ownerID = Configuration.instance.getUserID() ?? 0;
// We use a lock to prevent synchronisation to occur while it is downloading
// as this introduces wrong entry in FilesDB due to race condition
// This is a fix for https://github.com/ente-io/ente/issues/4296
@@ -98,7 +98,8 @@ class LocalSyncService {
);
} else {
// Load from 0 - 01.01.2010
Bus.instance.fire(SyncStatusUpdate(SyncStatus.startedFirstGalleryImport));
Bus.instance
.fire(SyncStatusUpdate(SyncStatus.startedFirstGalleryImport));
var startTime = 0;
var toYear = 2010;
var toTime = DateTime(toYear).microsecondsSinceEpoch;

View File

@@ -5,6 +5,7 @@ import "package:battery_info/battery_info_plugin.dart";
import "package:battery_info/model/android_battery_info.dart";
import "package:battery_info/model/iso_battery_info.dart";
import "package:logging/logging.dart";
import "package:photos/core/configuration.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/events/machine_learning_control_event.dart";
@@ -25,6 +26,9 @@ class MachineLearningController {
bool get isDeviceHealthy => _isDeviceHealthy;
MachineLearningController() {
if (Configuration.instance.getUserID() == null) {
return;
}
_logger.info('MachineLearningController constructor');
_startInteractionTimer(kDefaultInteractionTimeout);
if (Platform.isIOS) {
@@ -45,6 +49,9 @@ class MachineLearningController {
}
void onUserInteraction() {
if (Configuration.instance.getUserID() == null) {
return;
}
if (!_isUserInteracting) {
_logger.info("User is interacting with the app");
_isUserInteracting = true;

View File

@@ -193,28 +193,6 @@ class MLIndexingIsolate extends SuperIsolate {
}
}
/// WARNING: This method is only for debugging purposes. It should not be used in production.
Future<void> debugLoadSingleModel(MLModels model) {
return _initModelLock.synchronized(() async {
final modelInstance = model.model;
if (modelInstance.isInitialized) {
_logger.info("Model ${model.name} already loaded");
return;
}
final modelName = modelInstance.modelName;
final modelPath = await modelInstance.downloadModelSafe();
if (modelPath == null) {
_logger.severe("Could not download model, no wifi");
return;
}
final address = await runInIsolate(IsolateOperation.loadModel, {
"modelName": modelName,
"modelPath": modelPath,
}) as int;
modelInstance.storeSessionAddress(address);
});
}
Future<void> cleanupLocalIndexingModels({bool delete = false}) async {
if (!areModelsDownloaded) return;
await _releaseModels();

View File

@@ -82,7 +82,7 @@ class PreviewVideoStore {
Bus.instance.fire(VideoStreamingChanged());
if (isVideoStreamingEnabled) {
putFilesForPreviewCreation().ignore();
await putFilesForPreviewCreation();
} else {
clearQueue();
}
@@ -135,6 +135,22 @@ class PreviewVideoStore {
}
}
final fileSize = file.lengthSync();
FFProbeProps? props;
if (fileSize <= 10 * 1024 * 1024) {
props = await getVideoPropsAsync(file);
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
final codec = videoData["codec_name"]?.toString().toLowerCase();
final codecIsH264 = codec?.contains("h264") ?? false;
if (codecIsH264) {
_items.removeWhere((key, value) => value.file == enteFile);
Bus.instance.fire(PreviewUpdatedEvent(_items));
return;
}
}
if (uploadingFileId >= 0) {
_items[enteFile.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.inQueue,
@@ -158,8 +174,7 @@ class PreviewVideoStore {
);
Bus.instance.fire(PreviewUpdatedEvent(_items));
final props = await getVideoPropsAsync(file);
final fileSize = enteFile.fileSize ?? file.lengthSync();
props ??= await getVideoPropsAsync(file);
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
@@ -187,16 +202,15 @@ class PreviewVideoStore {
);
FFmpegSession? session;
final colorSpace = videoData["color_space"]?.toString().toLowerCase();
final isColorGood = colorSpace == "bt709";
final codecIsH264 = codec?.contains("h264") ?? false;
if (bitrate != null && bitrate <= 4000 * 1000 && codecIsH264) {
// create playlist without compression, as is
session = await FFmpegKit.execute(
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 '
'-c:v copy -c:a copy '
'-f hls -hls_time 2 -hls_flags single_file '
'-metadata:s:v:0 rotate=0 ' // Adjust metadata if needed
'-c:v copy ' // Copy the original video codec
'-c:a copy ' // Copy the original audio codec
'-f hls -hls_time 10 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -204,26 +218,16 @@ class PreviewVideoStore {
codec != null &&
bitrate <= 2000 * 1000 &&
!codecIsH264) {
// compress video with crf=21, h264 no change in resolution or frame rate,
// just change color scheme
session = await FFmpegKit.execute(
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 '
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
'-c:v libx264 -crf 23 -preset medium '
'-c:a copy '
'-f hls -hls_time 2 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
}
if (colorSpace != null && isColorGood) {
session ??= await FFmpegKit.execute(
'-i "${file.path}" '
'-metadata:s:v:0 rotate=0 '
'-vf "scale=-2:720,fps=30" '
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
'-metadata:s:v:0 rotate=0 ' // Keep rotation metadata
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' // Adjust color scheme
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' // Set color profile to BT.709
'-c:v libx264 -crf 21 -preset medium ' // Compress with CRF=21 using H.264
'-c:a copy ' // Keep original audio
'-f hls -hls_time 10 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -235,8 +239,8 @@ class PreviewVideoStore {
'-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
'-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" '
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
'-c:v libx264 -b:v 2000k -preset medium '
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
'$prefix/output.m3u8',
);
@@ -312,7 +316,6 @@ class PreviewVideoStore {
}
if (error == null) {
FileDataService.instance.syncFDStatus().ignore();
_items[enteFile.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.uploaded,
file: enteFile,
@@ -369,7 +372,6 @@ class PreviewVideoStore {
'type': 'hls_video',
'width': width,
'height': height,
'size': objectSize,
},
encryptionKey,
);
@@ -477,7 +479,7 @@ class PreviewVideoStore {
width = playlistData["width"];
height = playlistData["height"];
size = playlistData["size"];
size = response.data["data"]["objectSize"];
if (objectKey != null) {
unawaited(
@@ -582,49 +584,21 @@ class PreviewVideoStore {
final files = await FilesDB.instance.getAllFilesAfterDate(
fileType: FileType.video,
beginDate: cutoff,
userID: Configuration.instance.getUserID()!,
);
final previewIds = FileDataService.instance.previewIds;
final allFiles = files
.where((file) => previewIds?[file.uploadedFileID] == null)
.sorted((a, b) {
// put higher duration videos last
final first = a.duration == null || a.duration! >= 10 * 60 ? 1 : 0;
final second = b.duration == null || b.duration! >= 10 * 60 ? 1 : 0;
return first.compareTo(second);
}).toList();
.toList();
// set all video status to be in queue
for (final enteFile in allFiles) {
final fileSize = enteFile.fileSize;
FFProbeProps? props;
if (fileSize != null && fileSize <= 10 * 1024 * 1024) {
final file = await getFile(enteFile, isOrigin: true);
if (file != null) {
props = await getVideoPropsAsync(file);
final videoData = List.from(props?.propData?["streams"] ?? [])
.firstWhereOrNull((e) => e["type"] == "video");
final codec = videoData["codec_name"]?.toString().toLowerCase();
final codecIsH264 = codec?.contains("h264") ?? false;
if (codecIsH264) {
_items.removeWhere((key, value) => value.file == enteFile);
Bus.instance.fire(PreviewUpdatedEvent(_items));
continue;
}
}
}
_items[enteFile.uploadedFileID!] = PreviewItem(
for (final file in allFiles) {
_items[file.uploadedFileID!] = PreviewItem(
status: PreviewItemStatus.inQueue,
file: enteFile,
collectionID: enteFile.collectionID ?? 0,
file: file,
collectionID: file.collectionID ?? 0,
);
}
Bus.instance.fire(PreviewUpdatedEvent(_items));
final file = allFiles.first;

View File

@@ -15,7 +15,6 @@ import "package:photos/db/ml/db.dart";
import 'package:photos/events/local_photos_updated_event.dart';
import "package:photos/extensions/user_extension.dart";
import "package:photos/models/api/collection/user.dart";
import "package:photos/models/base_location.dart";
import 'package:photos/models/collection/collection.dart';
import 'package:photos/models/collection/collection_items.dart';
import "package:photos/models/file/extensions/file_props.dart";
@@ -36,7 +35,6 @@ import "package:photos/models/search/hierarchical/magic_filter.dart";
import "package:photos/models/search/hierarchical/top_level_generic_filter.dart";
import "package:photos/models/search/search_constants.dart";
import "package:photos/models/search/search_types.dart";
import "package:photos/models/trip_memory.dart";
import "package:photos/service_locator.dart";
import 'package:photos/services/collections_service.dart';
import "package:photos/services/filter/db_filters.dart";
@@ -1191,413 +1189,11 @@ class SearchService {
return searchResults;
}
Future<List<GenericSearchResult>> getTripsResults(
BuildContext context,
int? limit,
) async {
final List<GenericSearchResult> searchResults = [];
final allFiles = await getAllFilesForSearch();
final Iterable<LocalEntity<LocationTag>> locationTagEntities =
(await locationService.getLocationTags());
if (allFiles.isEmpty) return [];
final currentTime = DateTime.now().toLocal();
final currentMonth = currentTime.month;
final cutOffTime = currentTime.subtract(const Duration(days: 365));
final Map<LocalEntity<LocationTag>, List<EnteFile>> tagToItemsMap = {};
for (int i = 0; i < locationTagEntities.length; i++) {
tagToItemsMap[locationTagEntities.elementAt(i)] = [];
}
final List<(List<EnteFile>, Location)> smallRadiusClusters = [];
final List<(List<EnteFile>, Location)> wideRadiusClusters = [];
// Go through all files and cluster the ones not inside any location tag
for (EnteFile file in allFiles) {
if (!file.hasLocation ||
file.uploadedFileID == null ||
!file.isOwner ||
file.creationTime == null) {
continue;
}
// Check if the file is inside any location tag
bool hasLocationTag = false;
for (LocalEntity<LocationTag> tag in tagToItemsMap.keys) {
if (isFileInsideLocationTag(
tag.item.centerPoint,
file.location!,
tag.item.radius,
)) {
hasLocationTag = true;
tagToItemsMap[tag]!.add(file);
}
}
// Cluster the files not inside any location tag (incremental clustering)
if (!hasLocationTag) {
// Small radius clustering for base locations
bool foundSmallCluster = false;
for (final cluster in smallRadiusClusters) {
final clusterLocation = cluster.$2;
if (isFileInsideLocationTag(
clusterLocation,
file.location!,
0.6,
)) {
cluster.$1.add(file);
foundSmallCluster = true;
break;
}
}
if (!foundSmallCluster) {
smallRadiusClusters.add(([file], file.location!));
}
// Wide radius clustering for trip locations
bool foundWideCluster = false;
for (final cluster in wideRadiusClusters) {
final clusterLocation = cluster.$2;
if (isFileInsideLocationTag(
clusterLocation,
file.location!,
100.0,
)) {
cluster.$1.add(file);
foundWideCluster = true;
break;
}
}
if (!foundWideCluster) {
wideRadiusClusters.add(([file], file.location!));
}
}
}
// Identify base locations
final List<BaseLocation> baseLocations = [];
for (final cluster in smallRadiusClusters) {
final files = cluster.$1;
final location = cluster.$2;
// Check that the photos are distributed over a longer time range (3+ months)
final creationTimes = <int>[];
final Set<int> uniqueDays = {};
for (final file in files) {
creationTimes.add(file.creationTime!);
final date = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
final dayStamp =
DateTime(date.year, date.month, date.day).microsecondsSinceEpoch;
uniqueDays.add(dayStamp);
}
creationTimes.sort();
if (creationTimes.length < 10) continue;
final firstCreationTime = DateTime.fromMicrosecondsSinceEpoch(
creationTimes.first,
);
final lastCreationTime = DateTime.fromMicrosecondsSinceEpoch(
creationTimes.last,
);
if (lastCreationTime.difference(firstCreationTime).inDays < 90) {
continue;
}
// Check for a minimum average number of days photos are clicked in range
final daysRange = lastCreationTime.difference(firstCreationTime).inDays;
if (uniqueDays.length < daysRange * 0.1) continue;
// Check if it's a current or old base location
final bool isCurrent = lastCreationTime.isAfter(
DateTime.now().subtract(
const Duration(days: 90),
),
);
baseLocations.add(BaseLocation(files, location, isCurrent));
}
// Identify trip locations
final List<TripMemory> tripLocations = [];
clusteredLocations:
for (final cluster in wideRadiusClusters) {
final files = cluster.$1;
final location = cluster.$2;
// Check that it's at least 10km away from any base or tag location
bool tooClose = false;
for (final baseLocation in baseLocations) {
if (isFileInsideLocationTag(
baseLocation.location,
location,
10.0,
)) {
tooClose = true;
break;
}
}
for (final tag in tagToItemsMap.keys) {
if (isFileInsideLocationTag(
tag.item.centerPoint,
location,
10.0,
)) {
tooClose = true;
break;
}
}
if (tooClose) continue clusteredLocations;
// Check that the photos are distributed over a short time range (2-30 days) or multiple short time ranges only
files.sort((a, b) => a.creationTime!.compareTo(b.creationTime!));
// Find distinct time blocks (potential trips)
List<EnteFile> currentBlockFiles = [files.first];
int blockStart = files.first.creationTime!;
int lastTime = files.first.creationTime!;
DateTime lastDateTime = DateTime.fromMicrosecondsSinceEpoch(lastTime);
for (int i = 1; i < files.length; i++) {
final currentFile = files[i];
final currentTime = currentFile.creationTime!;
final gap = DateTime.fromMicrosecondsSinceEpoch(currentTime)
.difference(lastDateTime)
.inDays;
// If gap is too large, end current block and check if it's a valid trip
if (gap > 15) {
// 10 days gap to separate trips. If gap is small, it's likely not a trip
if (gap < 90) continue clusteredLocations;
final blockDuration = lastDateTime
.difference(DateTime.fromMicrosecondsSinceEpoch(blockStart))
.inDays;
// Check if current block is a valid trip (2-30 days)
if (blockDuration >= 2 && blockDuration <= 30) {
tripLocations.add(
TripMemory(
List.from(currentBlockFiles),
location,
blockStart,
lastTime,
),
);
}
// Start new block
currentBlockFiles = [];
blockStart = currentTime;
}
currentBlockFiles.add(currentFile);
lastTime = currentTime;
lastDateTime = DateTime.fromMicrosecondsSinceEpoch(lastTime);
}
// Check final block
final lastBlockDuration = lastDateTime
.difference(DateTime.fromMicrosecondsSinceEpoch(blockStart))
.inDays;
if (lastBlockDuration >= 2 && lastBlockDuration <= 30) {
tripLocations.add(
TripMemory(
List.from(currentBlockFiles),
location,
blockStart,
lastTime,
),
);
}
}
// Check if any trip locations should be merged
final List<TripMemory> mergedTrips = [];
for (final trip in tripLocations) {
final tripFirstTime = DateTime.fromMicrosecondsSinceEpoch(
trip.firstCreationTime,
);
final tripLastTime = DateTime.fromMicrosecondsSinceEpoch(
trip.lastCreationTime,
);
bool merged = false;
for (int idx = 0; idx < mergedTrips.length; idx++) {
final otherTrip = mergedTrips[idx];
final otherTripFirstTime =
DateTime.fromMicrosecondsSinceEpoch(otherTrip.firstCreationTime);
final otherTripLastTime =
DateTime.fromMicrosecondsSinceEpoch(otherTrip.lastCreationTime);
if (tripFirstTime
.isBefore(otherTripLastTime.add(const Duration(days: 3))) &&
tripLastTime.isAfter(
otherTripFirstTime.subtract(const Duration(days: 3)),
)) {
mergedTrips[idx] = TripMemory(
otherTrip.files + trip.files,
otherTrip.location,
min(otherTrip.firstCreationTime, trip.firstCreationTime),
max(otherTrip.lastCreationTime, trip.lastCreationTime),
);
_logger.finest('Merged two trip locations');
merged = true;
break;
}
}
if (merged) continue;
mergedTrips.add(
TripMemory(
trip.files,
trip.location,
trip.firstCreationTime,
trip.lastCreationTime,
),
);
}
// Remove too small and too recent trips
final List<TripMemory> validTrips = [];
for (final trip in mergedTrips) {
if (trip.files.length >= 20 &&
trip.averageCreationTime < cutOffTime.microsecondsSinceEpoch) {
validTrips.add(trip);
}
}
// For now for testing let's just surface all base locations
for (final baseLocation in baseLocations) {
String name = "Base (${baseLocation.isCurrentBase ? 'current' : 'old'})";
final String? locationName = await _tryFindLocationName(
baseLocation.files,
base: true,
);
if (locationName != null) {
name =
"$locationName (Base, ${baseLocation.isCurrentBase ? 'current' : 'old'})";
}
searchResults.add(
GenericSearchResult(
ResultType.event,
name,
baseLocation.files,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(baseLocation.files),
filterIcon: Icons.event_outlined,
),
),
);
}
// For now we surface the two most recent trips of current month, and if none, the earliest upcoming redundant trip
// Group the trips per month and then year
final Map<int, Map<int, List<TripMemory>>> tripsByMonthYear = {};
for (final trip in validTrips) {
final tripDate =
DateTime.fromMicrosecondsSinceEpoch(trip.averageCreationTime);
tripsByMonthYear
.putIfAbsent(tripDate.month, () => {})
.putIfAbsent(tripDate.year, () => [])
.add(trip);
}
// Flatten trips for the current month and annotate with their average date.
final List<TripMemory> currentMonthTrips = [];
if (tripsByMonthYear.containsKey(currentMonth)) {
for (final trips in tripsByMonthYear[currentMonth]!.values) {
for (final trip in trips) {
currentMonthTrips.add(trip);
}
}
}
// If there are past trips this month, show the one or two most recent ones.
if (currentMonthTrips.isNotEmpty) {
currentMonthTrips.sort(
(a, b) => b.averageCreationTime.compareTo(a.averageCreationTime),
);
final tripsToShow = currentMonthTrips.take(2);
for (final trip in tripsToShow) {
final year =
DateTime.fromMicrosecondsSinceEpoch(trip.averageCreationTime).year;
final String? locationName = await _tryFindLocationName(trip.files);
String name = "Trip in $year";
if (locationName != null) {
name = "Trip to $locationName";
} else if (year == currentTime.year - 1) {
name = "Last year's trip";
}
final photoSelection = await _bestSelection(trip.files);
searchResults.add(
GenericSearchResult(
ResultType.event,
name,
photoSelection,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(photoSelection),
filterIcon: Icons.event_outlined,
),
),
);
if (limit != null && searchResults.length >= limit) {
return searchResults;
}
}
}
// Otherwise, if no trips happened in the current month,
// look for the earliest upcoming trip in another month that has 3+ trips.
else {
// TODO lau: make sure the same upcoming trip isn't shown multiple times over multiple months
final sortedUpcomingMonths =
List<int>.generate(12, (i) => ((currentMonth + i) % 12) + 1);
checkUpcomingMonths:
for (final month in sortedUpcomingMonths) {
if (tripsByMonthYear.containsKey(month)) {
final List<TripMemory> thatMonthTrips = [];
for (final trips in tripsByMonthYear[month]!.values) {
for (final trip in trips) {
thatMonthTrips.add(trip);
}
}
if (thatMonthTrips.length >= 3) {
// take and use the third earliest trip
thatMonthTrips.sort(
(a, b) => a.averageCreationTime.compareTo(b.averageCreationTime),
);
final trip = thatMonthTrips[2];
final year =
DateTime.fromMicrosecondsSinceEpoch(trip.averageCreationTime)
.year;
final String? locationName = await _tryFindLocationName(trip.files);
String name = "Trip in $year";
if (locationName != null) {
name = "Trip to $locationName";
} else if (year == currentTime.year - 1) {
name = "Last year's trip";
}
final photoSelection = await _bestSelection(trip.files);
searchResults.add(
GenericSearchResult(
ResultType.event,
name,
photoSelection,
hierarchicalSearchFilter: TopLevelGenericFilter(
filterName: name,
occurrence: kMostRelevantFilter,
filterResultType: ResultType.event,
matchedUploadedIDs: filesToUploadedFileIDs(photoSelection),
filterIcon: Icons.event_outlined,
),
),
);
break checkUpcomingMonths;
}
}
}
}
return searchResults;
}
Future<List<GenericSearchResult>> onThisDayOrWeekResults(
BuildContext context,
int? limit,
) async {
final List<GenericSearchResult> searchResults = [];
final trips = await getTripsResults(context, limit);
if (trips.isNotEmpty) {
searchResults.addAll(trips);
}
final allFiles = await getAllFilesForSearch();
if (allFiles.isEmpty) return [];
@@ -1859,26 +1455,6 @@ class SearchService {
return ((dayOfYear - 1) ~/ 7) + 1;
}
Future<String?> _tryFindLocationName(
List<EnteFile> files, {
bool base = false,
}) async {
final results = await locationService.getFilesInCity(files, '');
final List<City> sortedByResultCount = results.keys.toList()
..sort((a, b) => results[b]!.length.compareTo(results[a]!.length));
if (sortedByResultCount.isEmpty) return null;
final biggestPlace = sortedByResultCount.first;
if (results[biggestPlace]!.length > files.length / 2) {
return biggestPlace.city;
}
if (results.length > 2 &&
results.keys.map((city) => city.country).toSet().length == 1 &&
!base) {
return biggestPlace.country;
}
return null;
}
/// Returns the best selection of files from the given list.
/// Makes sure that the selection is not more than [prefferedSize] or 10 files,
/// and that each year of the original list is represented.

View File

@@ -133,11 +133,11 @@ class SyncService {
),
);
} catch (e) {
if (e is DioException) {
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.sendTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.unknown) {
if (e is DioError) {
if (e.type == DioErrorType.connectTimeout ||
e.type == DioErrorType.sendTimeout ||
e.type == DioErrorType.receiveTimeout ||
e.type == DioErrorType.other) {
Bus.instance.fire(
SyncStatusUpdate(
SyncStatus.paused,

View File

@@ -124,7 +124,7 @@ class UserService {
} else {
throw Exception("send-ott action failed, non-200");
}
} on DioException catch (e) {
} on DioError catch (e) {
await dialog.hide();
_logger.info(e);
final String? enteErrCode = e.response?.data["code"];
@@ -185,7 +185,7 @@ class UserService {
);
final publicKey = response.data["publicKey"];
return publicKey;
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response?.statusCode == 404) {
return null;
}
@@ -221,7 +221,7 @@ class UserService {
}
}
return userDetails;
} on DioException catch (e) {
} on DioError catch (e) {
_logger.info(e);
rethrow;
}
@@ -231,7 +231,7 @@ class UserService {
try {
final response = await _enteDio.get("/users/sessions");
return Sessions.fromMap(response.data);
} on DioException catch (e) {
} on DioError catch (e) {
_logger.info(e);
rethrow;
}
@@ -245,7 +245,7 @@ class UserService {
"token": token,
},
);
} on DioException catch (e) {
} on DioError catch (e) {
_logger.info(e);
rethrow;
}
@@ -254,7 +254,7 @@ class UserService {
Future<void> leaveFamilyPlan() async {
try {
await _enteDio.delete("/family/leave");
} on DioException catch (e) {
} on DioError catch (e) {
_logger.warning('failed to leave family plan', e);
rethrow;
}
@@ -271,7 +271,7 @@ class UserService {
}
} catch (e) {
// check if token is already invalid
if (e is DioException && e.response?.statusCode == 401) {
if (e is DioError && e.response?.statusCode == 401) {
await Configuration.instance.logout();
Navigator.of(context).popUntil((route) => route.isFirst);
return;
@@ -342,7 +342,7 @@ class UserService {
},
);
return response.data;
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null) {
if (e.response!.statusCode == 404 || e.response!.statusCode == 410) {
throw PassKeySessionExpiredError();
@@ -460,7 +460,7 @@ class UserService {
// should never reach here
throw Exception("unexpected response during email verification");
}
} on DioException catch (e) {
} on DioError catch (e) {
_logger.info(e);
await dialog.hide();
if (e.response != null && e.response!.statusCode == 410) {
@@ -532,7 +532,7 @@ class UserService {
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
);
} on DioException catch (e) {
} on DioError catch (e) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 403) {
// ignore: unawaited_futures
@@ -592,7 +592,7 @@ class UserService {
} else {
throw Exception("get-srp-attributes action failed");
}
} on DioException catch (e) {
} on DioError catch (e) {
if (e.response != null && e.response!.statusCode == 404) {
throw SrpSetupNotCompleteError();
}
@@ -865,7 +865,7 @@ class UserService {
(route) => route.isFirst,
);
}
} on DioException catch (e) {
} on DioError catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -932,7 +932,7 @@ class UserService {
(route) => route.isFirst,
);
}
} on DioException catch (e) {
} on DioError catch (e) {
await dialog.hide();
_logger.severe('error while recovery 2fa', e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -1031,7 +1031,7 @@ class UserService {
(route) => route.isFirst,
);
}
} on DioException catch (e) {
} on DioError catch (e) {
await dialog.hide();
_logger.severe("error during recovery", e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -1126,7 +1126,7 @@ class UserService {
} catch (e, s) {
await dialog.hide();
_logger.severe(e, s);
if (e is DioException) {
if (e is DioError) {
if (e.response != null && e.response!.statusCode == 401) {
// ignore: unawaited_futures
showErrorDialog(
@@ -1311,30 +1311,34 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
}
}
}
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
// Add owners of collections shared with user
if (!existingEmails.contains(c.owner.email)) {
relevantUsers.add(c.owner);
existingEmails.add(c.owner.email);
if (!existingEmails.contains(c.owner!.email)) {
relevantUsers.add(c.owner!);
existingEmails.add(c.owner!.email);
}
// Add collaborators of collections shared with user where user is a
// viewer or a collaborator
for (final User u in c.sharees) {
if (u.id != null &&
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.email == ownerEmail &&
(u.isCollaborator || u.isViewer)) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
if (!existingEmails.contains(u.email)) {
relevantUsers.add(u);
existingEmails.add(u.email);
@@ -1388,28 +1392,32 @@ class UserService {
for (final c in CollectionsService.instance.getActiveCollections()) {
// Add collaborators and viewers of collections owned by user
if (c.owner.id == ownerID) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty) {
if (c.owner?.id == ownerID) {
for (final User? u in c.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}
}
}
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
// Add owners of collections shared with user
if (!emailIDs.contains(c.owner.email)) {
emailIDs.add(c.owner.email);
if (!emailIDs.contains(c.owner!.email)) {
emailIDs.add(c.owner!.email);
}
// Add collaborators of collections shared with user where user is a
// viewer or a collaborator
for (final User u in c.sharees) {
if (u.id != null &&
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.email == ownerEmail &&
(u.isCollaborator || u.isViewer)) {
for (final User u in c.sharees) {
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
for (final User? u in c.sharees ?? []) {
if (u != null &&
u.id != null &&
u.email.isNotEmpty &&
u.isCollaborator) {
if (!emailIDs.contains(u.email)) {
emailIDs.add(u.email);
}

View File

@@ -36,13 +36,11 @@ class _AllSectionsExamplesProviderState
late StreamSubscription<TabChangedEvent> _tabChangeEvent;
bool hasPendingUpdate = false;
bool isOnSearchTab = false;
bool _firstLoadInProgressOrComplete = false;
final _logger = Logger("AllSectionsExamplesProvider");
final _debouncer = Debouncer(
const Duration(seconds: 3),
executionInterval: const Duration(seconds: 12),
leading: true,
);
@override
@@ -68,12 +66,7 @@ class _AllSectionsExamplesProviderState
isOnSearchTab = false;
}
});
Future.delayed(const Duration(seconds: 3), () {
if (!_firstLoadInProgressOrComplete) {
reloadAllSections();
}
});
reloadAllSections();
}
void onDataUpdate() {
@@ -89,7 +82,6 @@ class _AllSectionsExamplesProviderState
}
void reloadAllSections() {
_firstLoadInProgressOrComplete = true;
_logger.info('queue reload all sections');
_debouncer.run(() async {
setState(() {

View File

@@ -113,7 +113,7 @@ class _LoginPasswordVerificationPageState
password,
dialog,
);
} on DioException catch (e, s) {
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
@@ -123,10 +123,8 @@ class _LoginPasswordVerificationPageState
S.of(context).pleaseTryAgain,
);
} else {
_logger.severe('API failure during SRP login ${e.type}', e, s);
if (e.type == DioExceptionType.connectionTimeout ||
e.type == DioExceptionType.receiveTimeout ||
e.type == DioExceptionType.sendTimeout) {
_logger.severe('API failure during SRP login', e, s);
if (e.type == DioErrorType.other) {
await _showContactSupportDialog(
context,
S.of(context).noInternetConnection,

View File

@@ -43,7 +43,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
await userRemoteFlagService.markRecoveryVerificationAsDone();
} catch (e) {
await dialog.hide();
if (e is DioException && e.type == DioExceptionType.connectionError) {
if (e is DioError && e.type == DioErrorType.other) {
await showErrorDialog(
context,
S.of(context).noInternetConnection,

View File

@@ -147,7 +147,7 @@ extension CollectionFileActions on CollectionActions {
// Newly created collection might not be cached
final Collection? c =
CollectionsService.instance.getCollectionByID(collectionID);
if (c != null && c.owner.id != currentUserID) {
if (c != null && c.owner!.id != currentUserID) {
if (!showProgressDialog) {
dialog = createProgressDialog(
context,

View File

@@ -340,7 +340,7 @@ class CollectionActions {
) async {
final textTheme = getEnteTextTheme(bContext);
final currentUserID = Configuration.instance.getUserID()!;
if (collection.owner.id != currentUserID) {
if (collection.owner!.id != currentUserID) {
throw AssertionError("Can not delete album owned by others");
}
if (collection.hasSharees) {
@@ -495,7 +495,7 @@ class CollectionActions {
bool isHidden = false,
}) async {
final int currentUserID = Configuration.instance.getUserID()!;
final isCollectionOwner = collection.owner.id == currentUserID;
final isCollectionOwner = collection.owner!.id == currentUserID;
final FilesSplit split = FilesSplit.split(
files,
Configuration.instance.getUserID()!,
@@ -631,7 +631,7 @@ class CollectionActions {
if (targetCollection == null ||
(CollectionType.uncategorized == targetCollection.type ||
targetCollection.type == CollectionType.favorites) ||
targetCollection.owner.id != userID) {
targetCollection.owner!.id != userID) {
return false;
}
return true;

View File

@@ -41,7 +41,7 @@ class AlbumRowItemWidget extends StatelessWidget {
final Widget? linkIcon = c.hasLink && isOwner
? Icon(
Icons.link,
color: c.publicURLs.first.isExpired ? warning500 : strokeBaseDark,
color: c.publicURLs!.first!.isExpired ? warning500 : strokeBaseDark,
)
: null;
return GestureDetector(
@@ -115,7 +115,7 @@ class AlbumRowItemWidget extends StatelessWidget {
bottom: 8.0,
),
child: UserAvatarWidget(
c.owner,
c.owner!,
thumbnailView: true,
),
),

View File

@@ -287,8 +287,8 @@ class AlbumVerticalListWidget extends StatelessWidget {
CollectionActions(CollectionsService.instance);
if (collection.hasLink) {
if (collection.publicURLs.first.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner.id) {
if (collection.publicURLs!.first!.enableCollect) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,
@@ -334,7 +334,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
context,
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
);
if (Configuration.instance.getUserID() == collection.owner.id) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,
@@ -353,7 +353,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
BuildContext context,
Collection collection,
) {
if (Configuration.instance.getUserID() == collection.owner.id) {
if (Configuration.instance.getUserID() == collection.owner!.id) {
unawaited(
routeToPage(
context,

View File

@@ -16,8 +16,8 @@ import "package:photos/utils/debouncer.dart";
class DeviceFoldersGridView extends StatefulWidget {
const DeviceFoldersGridView({
super.key,
});
Key? key,
}) : super(key: key);
@override
State<DeviceFoldersGridView> createState() => _DeviceFoldersGridViewState();
@@ -31,12 +31,10 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
leading: true,
);
@override
void initState() {
super.initState();
_backupFoldersUpdatedEvent =
Bus.instance.on<BackupFoldersUpdatedEvent>().listen((event) {
_loadReason = event.reason;
@@ -53,6 +51,8 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
}
});
});
super.initState();
}
@override

View File

@@ -74,7 +74,6 @@ class _DeviceFolderVerticalGridViewBodyState
@override
void initState() {
super.initState();
_backupFoldersUpdatedEvent =
Bus.instance.on<BackupFoldersUpdatedEvent>().listen((event) {
_loadReason = event.reason;
@@ -91,6 +90,7 @@ class _DeviceFolderVerticalGridViewBodyState
}
});
});
super.initState();
}
@override

View File

@@ -108,7 +108,7 @@ class ReferralCodeWidget extends StatelessWidget {
notifyParent?.call();
} catch (e, s) {
Logger("ReferralCodeWidget").severe("Failed to update code", e, s);
if (e is DioException) {
if (e is DioError) {
if (e.response?.statusCode == 400) {
await showInfoDialog(
context,

View File

@@ -77,7 +77,7 @@ class _HomeGalleryWidgetState extends State<HomeGalleryWidget> {
final gallery = Gallery(
key: ValueKey(_shouldHideSharedItems),
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
final ownerID = Configuration.instance.getUserID();
final ownerID = Configuration.instance.getUserID() ?? 0;
final hasSelectedAllForBackup =
Configuration.instance.hasSelectedAllFoldersForBackup();
final collectionsToHide =
@@ -89,7 +89,7 @@ class _HomeGalleryWidgetState extends State<HomeGalleryWidget> {
ignoredCollectionIDs: collectionsToHide,
ignoreSavedFiles: true,
);
if (hasSelectedAllForBackup) {
if (hasSelectedAllForBackup || ownerID == 0) {
result = await FilesDB.instance.getAllLocalAndUploadedFiles(
creationStartTime,
creationEndTime,

View File

@@ -144,9 +144,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
forceCustomPageRoute: true,
).ignore();
},
child: RefreshIndicatorWidget(
S.of(context).processingVideos,
),
child: Text(S.of(context).processingVideos),
)
: const Text("ente", style: brandStyleMedium),
),
@@ -251,50 +249,7 @@ class _SyncStatusWidgetState extends State<SyncStatusWidget> {
if (_event!.status == SyncStatus.completedBackup) {
return const SyncStatusCompletedWidget();
}
return RefreshIndicatorWidget(_getRefreshingText(context));
}
String _getRefreshingText(BuildContext context) {
if (_event == null) {
return S.of(context).loadingGallery;
}
if (_event!.status == SyncStatus.startedFirstGalleryImport ||
_event!.status == SyncStatus.completedFirstGalleryImport) {
return S.of(context).loadingGallery;
}
if (_event!.status == SyncStatus.applyingRemoteDiff) {
return S.of(context).syncing;
}
if (_event!.status == SyncStatus.preparingForUpload) {
if (_event!.total == null || _event!.total! <= 0) {
return S.of(context).encryptingBackup;
} else if (_event!.total == 1) {
return S.of(context).uploadingSingleMemory;
} else {
return S
.of(context)
.uploadingMultipleMemories(NumberFormat().format(_event!.total!));
}
}
if (_event!.status == SyncStatus.inProgress) {
final format = NumberFormat();
return S.of(context).syncProgress(
format.format(_event!.completed!),
format.format(_event!.total!),
);
}
if (_event!.status == SyncStatus.paused) {
return _event!.reason;
}
if (_event!.status == SyncStatus.error) {
return _event!.reason;
}
if (_event!.status == SyncStatus.completedBackup) {
if (_event!.wasStopped) {
return S.of(context).syncStopped;
}
}
return S.of(context).allMemoriesPreserved;
return RefreshIndicatorWidget(_event);
}
}
@@ -304,9 +259,9 @@ class RefreshIndicatorWidget extends StatelessWidget {
valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(45, 194, 98, 1.0)),
);
final String text;
final SyncStatusUpdate? event;
const RefreshIndicatorWidget(this.text, {super.key});
const RefreshIndicatorWidget(this.event, {super.key});
@override
Widget build(BuildContext context) {
@@ -331,7 +286,7 @@ class RefreshIndicatorWidget extends StatelessWidget {
),
Padding(
padding: const EdgeInsets.fromLTRB(12, 4, 0, 0),
child: Text(text),
child: Text(_getRefreshingText(context)),
),
],
),
@@ -340,6 +295,49 @@ class RefreshIndicatorWidget extends StatelessWidget {
),
);
}
String _getRefreshingText(BuildContext context) {
if (event == null) {
return S.of(context).loadingGallery;
}
if (event!.status == SyncStatus.startedFirstGalleryImport ||
event!.status == SyncStatus.completedFirstGalleryImport) {
return S.of(context).loadingGallery;
}
if (event!.status == SyncStatus.applyingRemoteDiff) {
return S.of(context).syncing;
}
if (event!.status == SyncStatus.preparingForUpload) {
if (event!.total == null || event!.total! <= 0) {
return S.of(context).encryptingBackup;
} else if (event!.total == 1) {
return S.of(context).uploadingSingleMemory;
} else {
return S
.of(context)
.uploadingMultipleMemories(NumberFormat().format(event!.total!));
}
}
if (event!.status == SyncStatus.inProgress) {
final format = NumberFormat();
return S.of(context).syncProgress(
format.format(event!.completed!),
format.format(event!.total!),
);
}
if (event!.status == SyncStatus.paused) {
return event!.reason;
}
if (event!.status == SyncStatus.error) {
return event!.reason;
}
if (event!.status == SyncStatus.completedBackup) {
if (event!.wasStopped) {
return S.of(context).syncStopped;
}
}
return S.of(context).allMemoriesPreserved;
}
}
class SyncStatusCompletedWidget extends StatelessWidget {

View File

@@ -28,7 +28,7 @@ class MapView extends StatefulWidget {
static const defaultMarkerSize = Size(75, 75);
const MapView({
super.key,
Key? key,
required this.updateVisibleImages,
required this.imageMarkers,
required this.controller,
@@ -42,7 +42,7 @@ class MapView extends StatefulWidget {
this.onTap,
this.interactiveFlags = InteractiveFlag.all,
this.showControls = true,
});
}) : super(key: key);
@override
State<StatefulWidget> createState() => _MapViewState();

View File

@@ -117,26 +117,28 @@ class AdvancedSettingsScreen extends StatelessWidget {
},
),
),
const SizedBox(height: 24),
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).videoStreaming,
),
menuItemColor: colorScheme.fillFaint,
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
trailingWidget: ToggleSwitchWidget(
value: () => PreviewVideoStore
.instance.isVideoStreamingEnabled,
onChanged: () async {
final isEnabled = PreviewVideoStore
.instance.isVideoStreamingEnabled;
if (flagService.internalUser) ...[
const SizedBox(height: 24),
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).videoStreaming,
),
menuItemColor: colorScheme.fillFaint,
singleBorderRadius: 8,
alignCaptionedTextToLeft: true,
trailingWidget: ToggleSwitchWidget(
value: () => PreviewVideoStore
.instance.isVideoStreamingEnabled,
onChanged: () async {
final isEnabled = PreviewVideoStore
.instance.isVideoStreamingEnabled;
await PreviewVideoStore.instance
.setIsVideoStreamingEnabled(!isEnabled);
},
await PreviewVideoStore.instance
.setIsVideoStreamingEnabled(!isEnabled);
},
),
),
),
],
const SizedBox(
height: 24,
),

View File

@@ -7,11 +7,9 @@ import "package:photos/models/preview/preview_item.dart";
import "package:photos/models/preview/preview_item_status.dart";
import "package:photos/services/preview_video_store.dart";
import 'package:photos/theme/ente_theme.dart';
import "package:photos/ui/viewer/file/detail_page.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/file_uploader.dart";
import "package:photos/utils/navigation_util.dart";
class BackupItemCard extends StatefulWidget {
const BackupItemCard({
@@ -57,214 +55,199 @@ class _BackupItemCardState extends State<BackupItemCard> {
final hasError = widget.item.error != null ||
widget.preview?.status == PreviewItemStatus.failed;
return GestureDetector(
onTap: () {
routeToPage(
context,
DetailPage(
DetailPageConfiguration(
List.unmodifiable([widget.item.file]),
0,
"collection",
),
),
forceCustomPageRoute: true,
);
},
child: Container(
height: 60,
margin: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: colorScheme.fillFaint.withOpacity(0.08),
width: 1,
),
return Container(
height: 60,
margin: const EdgeInsets.symmetric(vertical: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
border: Border.all(
color: colorScheme.fillFaint.withOpacity(0.08),
width: 1,
),
child: Row(
children: [
SizedBox(
width: 60,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: showThumbnail
? ThumbnailWidget(
widget.item.file,
shouldShowSyncStatus: false,
)
: Container(
color: colorScheme.fillFaint, // Placeholder color
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.file.displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
height: 20 / 16,
color: Theme.of(context).brightness == Brightness.light
? const Color(0xFF000000)
: const Color(0xFFFFFFFF),
),
child: Row(
children: [
SizedBox(
width: 60,
height: 60,
child: ClipRRect(
borderRadius: BorderRadius.circular(4),
child: showThumbnail
? ThumbnailWidget(
widget.item.file,
shouldShowSyncStatus: false,
)
: Container(
color: colorScheme.fillFaint, // Placeholder color
),
),
const SizedBox(height: 4),
Text(
folderName ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
height: 17 / 14,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, 0.7)
: const Color.fromRGBO(255, 255, 255, 0.7),
),
),
],
),
),
if (hasError) const SizedBox(width: 12),
if (hasError)
SizedBox(
height: 48,
width: 48,
child: IconButton(
icon: Icon(
Icons.error_outline,
color: getEnteColorScheme(context).fillBase,
),
const SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.item.file.displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
height: 20 / 16,
color: Theme.of(context).brightness == Brightness.light
? const Color(0xFF000000)
: const Color(0xFFFFFFFF),
),
onPressed: () {
String errorMessage = "";
if (widget.item.error is Exception) {
final Exception ex = widget.item.error as Exception;
errorMessage = "Error: " +
ex.runtimeType.toString() +
" - " +
ex.toString();
} else if (widget.item.error != null) {
errorMessage = widget.item.error.toString();
} else if (widget.preview?.error != null) {
errorMessage = widget.preview!.error!.toString();
}
showErrorDialog(
context,
'Upload failed',
errorMessage,
);
},
),
),
if (hasError) const SizedBox(width: 12),
const SizedBox(height: 4),
Text(
folderName ?? "",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
height: 17 / 14,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, 0.7)
: const Color.fromRGBO(255, 255, 255, 0.7),
),
),
],
),
),
if (hasError) const SizedBox(width: 12),
if (hasError)
SizedBox(
height: 48,
width: 48,
child: Center(
child: switch (widget.item.status) {
BackupItemStatus.uploading => SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2.0,
color: colorScheme.primary700,
),
),
BackupItemStatus.uploaded => widget.preview != null
? switch (widget.preview!.status) {
PreviewItemStatus.uploading ||
PreviewItemStatus.compressing =>
SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video.png",
),
),
PreviewItemStatus.failed => GestureDetector(
onTap: () => PreviewVideoStore.instance
.chunkAndUploadVideo(
context,
widget.item.file,
true,
),
child: SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video-failed.png",
),
),
),
PreviewItemStatus.retry ||
PreviewItemStatus.inQueue =>
SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/video-processing-queued.png",
),
),
PreviewItemStatus.uploaded => SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video-success.png",
),
),
}
: const SizedBox(
width: 24,
height: 24,
child: Icon(
Icons.check,
color: Color(0xFF00B33C),
),
),
BackupItemStatus.inQueue => SizedBox(
width: 24,
height: 24,
child: Icon(
Icons.history,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, .6)
: const Color.fromRGBO(255, 255, 255, .6),
),
),
BackupItemStatus.retry => IconButton(
icon: const Icon(
Icons.sync,
color: Color(0xFFFDB816),
),
onPressed: () async {
await FileUploader.instance.upload(
widget.item.file,
widget.item.collectionID,
);
},
),
BackupItemStatus.inBackground => SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2.0,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, .6)
: const Color.fromRGBO(255, 255, 255, .6),
),
),
child: IconButton(
icon: Icon(
Icons.error_outline,
color: getEnteColorScheme(context).fillBase,
),
onPressed: () {
String errorMessage = "";
if (widget.item.error is Exception) {
final Exception ex = widget.item.error as Exception;
errorMessage = "Error: " +
ex.runtimeType.toString() +
" - " +
ex.toString();
} else if (widget.item.error != null) {
errorMessage = widget.item.error.toString();
} else if (widget.preview?.error != null) {
errorMessage = widget.preview!.error!.toString();
}
showErrorDialog(
context,
'Upload failed',
errorMessage,
);
},
),
),
],
),
if (hasError) const SizedBox(width: 12),
SizedBox(
height: 48,
width: 48,
child: Center(
child: switch (widget.item.status) {
BackupItemStatus.uploading => SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2.0,
color: colorScheme.primary700,
),
),
BackupItemStatus.uploaded => widget.preview != null
? switch (widget.preview!.status) {
PreviewItemStatus.uploading ||
PreviewItemStatus.compressing =>
SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video.png",
),
),
PreviewItemStatus.failed => GestureDetector(
onTap: () =>
PreviewVideoStore.instance.chunkAndUploadVideo(
context,
widget.item.file,
true,
),
child: SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video-failed.png",
),
),
),
PreviewItemStatus.retry ||
PreviewItemStatus.inQueue =>
SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/video-processing-queued.png",
),
),
PreviewItemStatus.uploaded => SizedBox(
width: 24,
height: 24,
child: Image.asset(
"assets/processing-video-success.png",
),
),
}
: const SizedBox(
width: 24,
height: 24,
child: Icon(
Icons.check,
color: Color(0xFF00B33C),
),
),
BackupItemStatus.inQueue => SizedBox(
width: 24,
height: 24,
child: Icon(
Icons.history,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, .6)
: const Color.fromRGBO(255, 255, 255, .6),
),
),
BackupItemStatus.retry => IconButton(
icon: const Icon(
Icons.sync,
color: Color(0xFFFDB816),
),
onPressed: () async {
await FileUploader.instance.upload(
widget.item.file,
widget.item.collectionID,
);
},
),
BackupItemStatus.inBackground => SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2.0,
color: Theme.of(context).brightness == Brightness.light
? const Color.fromRGBO(0, 0, 0, .6)
: const Color.fromRGBO(255, 255, 255, .6),
),
),
},
),
),
],
),
);
}

View File

@@ -5,8 +5,6 @@ import "package:photos/db/ml/base.dart";
import "package:photos/db/ml/db.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import "package:photos/services/machine_learning/ml_models_overview.dart";
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/buttons/button_widget.dart";
@@ -106,90 +104,6 @@ class _MLUserDeveloperOptionsState extends State<MLUserDeveloperOptions> {
isBottomBorderRadiusRemoved: true,
isGestureDetectorDisabled: true,
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load face detection model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.faceDetection);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load face recognition model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.faceEmbedding);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load clip image model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.clipImageEncoder);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SizedBox(height: 24),
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: "Load clip text model",
onTap: () async {
try {
await MLIndexingIsolate.instance
.debugLoadSingleModel(MLModels.clipTextEncoder);
} catch (e, s) {
_logger.severe(
"Could not load face detection model",
e,
s,
);
await showGenericErrorDialog(
context: context,
error: e,
);
}
},
),
const SafeArea(
child: SizedBox(
height: 12,

View File

@@ -363,8 +363,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
List<User> _getSuggestedUser() {
final Set<String> existingEmails = {};
for (final User u in widget.collection.sharees) {
if (u.id != null && u.email.isNotEmpty) {
for (final User? u in widget.collection.sharees ?? []) {
if (u != null && u.id != null && u.email.isNotEmpty) {
existingEmails.add(u.email);
}
}

View File

@@ -64,11 +64,11 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
@override
Widget build(BuildContext context) {
final isOwner =
widget.collection.owner.id == Configuration.instance.getUserID();
widget.collection.owner?.id == Configuration.instance.getUserID();
final colorScheme = getEnteColorScheme(context);
final currentUserID = Configuration.instance.getUserID()!;
final int participants = 1 + widget.collection.getSharees().length;
final User owner = widget.collection.owner;
final User owner = widget.collection.owner!;
if (owner.id == currentUserID && owner.email == "") {
owner.email = Configuration.instance.getEmail()!;
}
@@ -107,9 +107,11 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
captionedTextWidget: CaptionedTextWidget(
title: isOwner
? S.of(context).you
: _nameIfAvailableElseEmail(
widget.collection.owner,
),
: widget.collection.owner != null
? _nameIfAvailableElseEmail(
widget.collection.owner!,
)
: '',
makeTextBold: isOwner,
),
leadingIconWidget: UserAvatarWidget(

View File

@@ -47,13 +47,13 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
@override
Widget build(BuildContext context) {
final isCollectEnabled =
widget.collection!.publicURLs.firstOrNull?.enableCollect ?? false;
widget.collection!.publicURLs?.firstOrNull?.enableCollect ?? false;
final isDownloadEnabled =
widget.collection!.publicURLs.firstOrNull?.enableDownload ?? true;
widget.collection!.publicURLs?.firstOrNull?.enableDownload ?? true;
final isPasswordEnabled =
widget.collection!.publicURLs.firstOrNull?.passwordEnabled ?? false;
widget.collection!.publicURLs?.firstOrNull?.passwordEnabled ?? false;
final enteColorScheme = getEnteColorScheme(context);
final PublicURL url = widget.collection!.publicURLs.firstOrNull!;
final PublicURL url = widget.collection!.publicURLs!.firstOrNull!;
final String collectionKey = Base58Encode(
CollectionsService.instance.getCollectionKey(widget.collection!.id),
);

View File

@@ -72,7 +72,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
bool isCustomLimit = false;
@override
void initState() {
currentDeviceLimit = widget.collection.publicURLs.first.deviceLimit;
currentDeviceLimit = widget.collection.publicURLs!.first!.deviceLimit;
initialDeviceLimit = currentDeviceLimit;
if (!publicLinkDeviceLimits.contains(currentDeviceLimit)) {
isCustomLimit = true;

View File

@@ -61,7 +61,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
@override
Widget build(BuildContext context) {
_sharees = widget.collection.sharees;
_sharees = widget.collection.sharees ?? [];
final bool hasUrl = widget.collection.hasLink;
final children = <Widget>[];
children.add(
@@ -136,7 +136,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
}
final bool hasExpired =
widget.collection.publicURLs.firstOrNull?.isExpired ?? false;
widget.collection.publicURLs?.firstOrNull?.isExpired ?? false;
children.addAll([
const SizedBox(
height: 24,
@@ -166,7 +166,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
CollectionsService.instance.getCollectionKey(widget.collection.id),
);
final String url =
"${widget.collection.publicURLs.first.url}#$collectionKey";
"${widget.collection.publicURLs!.first!.url}#$collectionKey";
children.addAll(
[
MenuItemWidget(

View File

@@ -45,7 +45,6 @@ class _UserAvatarWidgetState extends State<UserAvatarWidget> {
final _debouncer = Debouncer(
const Duration(milliseconds: 250),
executionInterval: const Duration(seconds: 20),
leading: true,
);
@override

View File

@@ -275,7 +275,7 @@ class _HomeWidgetState extends State<HomeWidget> {
final existingCollection =
CollectionsService.instance.getCollectionByID(collection.id);
if (collection.isOwner(Configuration.instance.getUserID() ?? -1) ||
if (collection.owner!.id! == Configuration.instance.getUserID() ||
(existingCollection != null && !existingCollection.isDeleted)) {
await routeToPage(
context,
@@ -286,8 +286,8 @@ class _HomeWidgetState extends State<HomeWidget> {
return;
}
final dialog = createProgressDialog(context, "Loading...");
final publicUrl = collection.publicURLs[0];
if (!publicUrl.enableDownload) {
final publicUrl = collection.publicURLs![0];
if (!publicUrl!.enableDownload) {
await showErrorDialog(
context,
context.l10n.canNotOpenTitle,
@@ -440,7 +440,7 @@ class _HomeWidgetState extends State<HomeWidget> {
_intentDataStreamSubscription =
ReceiveSharingIntent.instance.getMediaStream().listen(
(List<SharedMediaFile> value) {
if (value.isNotEmpty && value[0].path.contains("albums.ente.io")) {
if (value[0].path.contains("albums.ente.io")) {
final uri = Uri.parse(value[0].path);
_handlePublicAlbumLink(uri);
return;
@@ -638,15 +638,18 @@ class _HomeWidgetState extends State<HomeWidget> {
);
}
bool isOfflineMode = true;
Widget _getBody(BuildContext context) {
if (!Configuration.instance.hasConfiguredAccount()) {
if (!Configuration.instance.hasConfiguredAccount() && !isOfflineMode) {
_closeDrawerIfOpen(context);
return const LandingPageWidget();
}
if (!LocalSyncService.instance.hasGrantedPermissions()) {
entityService.syncEntities().then((_) {
PersonService.instance.resetEmailToPartialPersonDataCache();
});
if (isOfflineMode) {
entityService.syncEntities().then((_) {
PersonService.instance.resetEmailToPartialPersonDataCache();
});
}
return const GrantPermissionsWidget();
}
if (!LocalSyncService.instance.hasCompletedFirstImport()) {
@@ -672,7 +675,8 @@ class _HomeWidgetState extends State<HomeWidget> {
_showShowBackupHook =
!Configuration.instance.hasSelectedAnyBackupFolder() &&
!LocalSyncService.instance.hasGrantedLimitedPermissions() &&
CollectionsService.instance.getActiveCollections().isEmpty;
CollectionsService.instance.getActiveCollections().isEmpty &&
!isOfflineMode;
return Stack(
children: [
@@ -694,15 +698,17 @@ class _HomeWidgetState extends State<HomeWidget> {
_showShowBackupHook
? const StartBackupHookWidget(headerWidget: HeaderWidget())
: HomeGalleryWidget(
header: const HeaderWidget(),
header: isOfflineMode
? const SizedBox.shrink()
: const HeaderWidget(),
footer: const SizedBox(
height: 160,
),
selectedFiles: _selectedFiles,
),
_userCollectionsTab,
_sharedCollectionTab,
_searchTab,
if (!isOfflineMode) _userCollectionsTab,
if (isOfflineMode) _sharedCollectionTab,
if (!isOfflineMode) _searchTab,
],
);
},
@@ -745,10 +751,11 @@ class _HomeWidgetState extends State<HomeWidget> {
),
)
: const SizedBox.shrink(),
HomeBottomNavigationBar(
_selectedFiles,
selectedTabIndex: _selectedTabIndex,
),
if (!isOfflineMode)
HomeBottomNavigationBar(
_selectedFiles,
selectedTabIndex: _selectedTabIndex,
),
],
),
);

View File

@@ -109,7 +109,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
style: textTheme.smallMuted,
),
c.hasLink
? (c.publicURLs.first.isExpired
? (c.publicURLs!.first!.isExpired
? Icon(
Icons.link_outlined,
color: colorScheme.warning500,

View File

@@ -3,6 +3,7 @@ import "dart:math";
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import "package:photos/core/configuration.dart";
import 'package:photos/core/event_bus.dart';
import 'package:photos/events/collection_updated_event.dart';
import 'package:photos/events/local_photos_updated_event.dart';
@@ -40,7 +41,6 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
leading: true,
);
static const heroTagPrefix = "outgoing_collection";
@@ -72,6 +72,11 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
@override
Widget build(BuildContext context) {
if (Configuration.instance.getUserID() == null) {
return const Center(
child: Text("Please login to view shared collections"),
);
}
super.build(context);
return FutureBuilder<SharedCollections>(
future: Future.value(CollectionsService.instance.getSharedCollections()),

View File

@@ -54,7 +54,6 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
leading: true,
);
static const int _kOnEnteItemLimitCount = 10;

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