Compare commits
2 Commits
streaming-
...
leap_of_fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e0eb9303f | ||
|
|
e1aee3cfbd |
9
.github/workflows/auth-internal-release.yml
vendored
@@ -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
|
||||
|
||||
11
.github/workflows/mobile-internal-release.yml
vendored
@@ -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
|
||||
|
||||
2
.github/workflows/mobile-release.yml
vendored
@@ -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"
|
||||
|
||||
25
.github/workflows/server-publish.yml
vendored
@@ -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 }}
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@@ -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 |
@@ -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": "تعذر إعراب الملف المنتقى."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "सेटिंग"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": "認証アカウントでログイン"
|
||||
}
|
||||
@@ -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": "സുരക്ഷ"
|
||||
}
|
||||
{}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
48
cli/main.go
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -138,10 +138,6 @@ export const sidebar = [
|
||||
text: "Machine Learning",
|
||||
link: "/photos/faq/machine-learning",
|
||||
},
|
||||
{
|
||||
text: "Video Streaming",
|
||||
link: "/photos/faq/video-streaming",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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).
|
||||
@@ -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 doesn’t 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -71,9 +71,9 @@ android {
|
||||
dimension "default"
|
||||
applicationIdSuffix ".dev"
|
||||
}
|
||||
face {
|
||||
offline {
|
||||
dimension "default"
|
||||
applicationIdSuffix ".face"
|
||||
applicationIdSuffix ".offline"
|
||||
}
|
||||
playstore {
|
||||
dimension "default"
|
||||
|
||||
2
mobile/android/app/proguard-rules.pro
vendored
@@ -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.** { *; }
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Ente Face</string>
|
||||
<string name="backup">backup face</string>
|
||||
</resources>
|
||||
4
mobile/android/app/src/offline/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="app_name">Ente Offline</string>
|
||||
<string name="backup">backup Offline</string>
|
||||
</resources>
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
12
mobile/lib/generated/intl/messages_en.dart
generated
@@ -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"),
|
||||
|
||||
78
mobile/lib/generated/l10n.dart
generated
@@ -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> {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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}",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -45,7 +45,6 @@ class _UserAvatarWidgetState extends State<UserAvatarWidget> {
|
||||
final _debouncer = Debouncer(
|
||||
const Duration(milliseconds: 250),
|
||||
executionInterval: const Duration(seconds: 20),
|
||||
leading: true,
|
||||
);
|
||||
|
||||
@override
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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;
|
||||
|
||||