Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
ca0474faca | |||
b469985277 | |||
2a5dacb460 | |||
d16f98cf07 | |||
8677cbb4f8 | |||
0e33299863 | |||
93ba4e011a | |||
7977bebcaa | |||
f28f49d724 | |||
d9a93ddad6 | |||
07808d6139 | |||
1e1633bb45 | |||
c0f33de0c8 | |||
417621b17c | |||
8322540732 | |||
2d61be37bb | |||
2a10aa7d61 | |||
004eb310b3 |
14
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -4,12 +4,11 @@ labels: ["triage"]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: >
|
||||||
Before opening a new bug report, please ensure
|
Before opening a new issue, please ensure you are on the latest
|
||||||
1. you are on the latest version (it might've already been fixed),
|
version (it might've already been fixed), and that you've searched
|
||||||
2. you've searched for existing issues (please add your observations as a comment there instead of creating a duplicate).
|
for existing issues (please add you observations as a comment
|
||||||
|
there instead of creating a duplicate).
|
||||||
If you are self hosting, please create a community [Q&A](https://github.com/ente-io/ente/discussions/categories/q-a) instead.
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Description
|
label: Description
|
||||||
|
@ -17,8 +16,7 @@ body:
|
||||||
Please describe the bug. If possible, also include the steps to
|
Please describe the bug. If possible, also include the steps to
|
||||||
reproduce the behaviour, and the expected behaviour (sometimes
|
reproduce the behaviour, and the expected behaviour (sometimes
|
||||||
bugs are just expectation mismatches, in which case this would be
|
bugs are just expectation mismatches, in which case this would be
|
||||||
a good fit for [feature
|
a good fit for Discussions).
|
||||||
requests](https://github.com/ente-io/ente/discussions/categories/feature-requests)).
|
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
|
3
.gitmodules
vendored
|
@ -20,3 +20,6 @@
|
||||||
path = web/apps/photos/thirdparty/photoswipe
|
path = web/apps/photos/thirdparty/photoswipe
|
||||||
url = https://github.com/ente-io/PhotoSwipe.git
|
url = https://github.com/ente-io/PhotoSwipe.git
|
||||||
branch = single-thread
|
branch = single-thread
|
||||||
|
[submodule "mobile/thirdparty/flutter"]
|
||||||
|
path = mobile/thirdparty/flutter
|
||||||
|
url = https://github.com/flutter/flutter
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
"codeIssuerHint": "発行者",
|
"codeIssuerHint": "発行者",
|
||||||
"codeSecretKeyHint": "秘密鍵",
|
"codeSecretKeyHint": "秘密鍵",
|
||||||
"codeAccountHint": "アカウント (you@domain.com)",
|
"codeAccountHint": "アカウント (you@domain.com)",
|
||||||
"codeTagHint": "タグ",
|
|
||||||
"accountKeyType": "鍵の種類",
|
|
||||||
"sessionExpired": "セッションが失効しました",
|
"sessionExpired": "セッションが失効しました",
|
||||||
"@sessionExpired": {
|
"@sessionExpired": {
|
||||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||||
|
@ -79,7 +77,6 @@
|
||||||
"data": "データ",
|
"data": "データ",
|
||||||
"importCodes": "コードをインポート",
|
"importCodes": "コードをインポート",
|
||||||
"importTypePlainText": "プレーンテキスト",
|
"importTypePlainText": "プレーンテキスト",
|
||||||
"importTypeEnteEncrypted": "Ente 暗号化されたエクスポート",
|
|
||||||
"passwordForDecryptingExport": "復号化用パスワード",
|
"passwordForDecryptingExport": "復号化用パスワード",
|
||||||
"passwordEmptyError": "パスワードは空欄にできません",
|
"passwordEmptyError": "パスワードは空欄にできません",
|
||||||
"importFromApp": "{appName} からコードをインポート",
|
"importFromApp": "{appName} からコードをインポート",
|
||||||
|
@ -124,7 +121,6 @@
|
||||||
"suggestFeatures": "機能を提案",
|
"suggestFeatures": "機能を提案",
|
||||||
"faq": "FAQ",
|
"faq": "FAQ",
|
||||||
"faq_q_1": "Authはどのくらい安全ですか?",
|
"faq_q_1": "Authはどのくらい安全ですか?",
|
||||||
"faq_a_1": "Ente Authでバックアップされたコードはすべてエンドツーエンドで暗号化されて保存されます。つまり、コードにアクセスできるのはあなただけです。当社のアプリはオープンソースであり、暗号化技術は外部監査を受けています。",
|
|
||||||
"faq_q_2": "パソコンから私のコードにアクセスできますか?",
|
"faq_q_2": "パソコンから私のコードにアクセスできますか?",
|
||||||
"faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。",
|
"faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。",
|
||||||
"faq_q_3": "コードを削除するにはどうすればいいですか?",
|
"faq_q_3": "コードを削除するにはどうすればいいですか?",
|
||||||
|
@ -158,7 +154,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"invalidQRCode": "QRコードが無効です",
|
|
||||||
"noRecoveryKeyTitle": "回復キーがありませんか?",
|
"noRecoveryKeyTitle": "回復キーがありませんか?",
|
||||||
"enterEmailHint": "メールアドレスを入力してください",
|
"enterEmailHint": "メールアドレスを入力してください",
|
||||||
"invalidEmailTitle": "メールアドレスが無効です",
|
"invalidEmailTitle": "メールアドレスが無効です",
|
||||||
|
@ -352,7 +347,6 @@
|
||||||
"deleteCodeAuthMessage": "コードを削除するためには認証が必要です",
|
"deleteCodeAuthMessage": "コードを削除するためには認証が必要です",
|
||||||
"showQRAuthMessage": "QR コードを表示するためには認証が必要です",
|
"showQRAuthMessage": "QR コードを表示するためには認証が必要です",
|
||||||
"confirmAccountDeleteTitle": "アカウントの削除に同意",
|
"confirmAccountDeleteTitle": "アカウントの削除に同意",
|
||||||
"confirmAccountDeleteMessage": "このアカウントは他のEnteアプリも使用している場合はそれらにも紐づけされています。\nすべてのEnteアプリでアップロードされたデータは削除され、アカウントは完全に削除されます。",
|
|
||||||
"androidBiometricHint": "本人を確認する",
|
"androidBiometricHint": "本人を確認する",
|
||||||
"@androidBiometricHint": {
|
"@androidBiometricHint": {
|
||||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||||
|
@ -423,18 +417,5 @@
|
||||||
"invalidEndpoint": "無効なエンドポイントです",
|
"invalidEndpoint": "無効なエンドポイントです",
|
||||||
"invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。",
|
"invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。",
|
||||||
"endpointUpdatedMessage": "エンドポイントの更新に成功しました",
|
"endpointUpdatedMessage": "エンドポイントの更新に成功しました",
|
||||||
"customEndpoint": "{endpoint} に接続しました",
|
"customEndpoint": "{endpoint} に接続しました"
|
||||||
"pinText": "固定",
|
|
||||||
"unpinText": "固定を解除",
|
|
||||||
"pinnedCodeMessage": "{code} を固定しました",
|
|
||||||
"unpinnedCodeMessage": "{code} の固定が解除されました",
|
|
||||||
"tags": "タグ",
|
|
||||||
"createNewTag": "新しいタグの作成",
|
|
||||||
"tag": "タグ",
|
|
||||||
"create": "作成",
|
|
||||||
"editTag": "タグの編集",
|
|
||||||
"deleteTagTitle": "タグを削除しますか?",
|
|
||||||
"deleteTagMessage": "このタグを削除してもよろしいですか?この操作は取り消しできません。",
|
|
||||||
"somethingWentWrongParsingCode": "{x} のコードを解析できませんでした。",
|
|
||||||
"updateNotAvailable": "アップデートは利用できません"
|
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@
|
||||||
"compare-versions": "^6.1",
|
"compare-versions": "^6.1",
|
||||||
"electron-log": "^5.1",
|
"electron-log": "^5.1",
|
||||||
"electron-store": "^8.2",
|
"electron-store": "^8.2",
|
||||||
"electron-updater": "^6.2",
|
"electron-updater": "^6.1",
|
||||||
"ffmpeg-static": "^5.2",
|
"ffmpeg-static": "^5.2",
|
||||||
"html-entities": "^2.5",
|
"html-entities": "^2.5",
|
||||||
"jpeg-js": "^0.4",
|
"jpeg-js": "^0.4",
|
||||||
|
|
|
@ -743,10 +743,10 @@ buffer@^5.1.0, buffer@^5.5.0:
|
||||||
base64-js "^1.3.1"
|
base64-js "^1.3.1"
|
||||||
ieee754 "^1.1.13"
|
ieee754 "^1.1.13"
|
||||||
|
|
||||||
builder-util-runtime@9.2.4:
|
builder-util-runtime@9.2.3:
|
||||||
version "9.2.4"
|
version "9.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz#13cd1763da621e53458739a1e63f7fcba673c42a"
|
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c"
|
||||||
integrity sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==
|
integrity sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
sax "^1.2.4"
|
sax "^1.2.4"
|
||||||
|
@ -1251,12 +1251,12 @@ electron-store@^8.2:
|
||||||
conf "^10.2.0"
|
conf "^10.2.0"
|
||||||
type-fest "^2.17.0"
|
type-fest "^2.17.0"
|
||||||
|
|
||||||
electron-updater@^6.2:
|
electron-updater@^6.1:
|
||||||
version "6.2.1"
|
version "6.1.8"
|
||||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.2.1.tgz#1c9adb9ba2a21a5dc50a8c434c45360d5e9fe6c9"
|
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.1.8.tgz#17637bca165322f4e526b13c99165f43e6f697d8"
|
||||||
integrity sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==
|
integrity sha512-hhOTfaFAd6wRHAfUaBhnAOYc+ymSGCWJLtFkw4xJqOvtpHmIdNHnXDV9m1MHC+A6q08Abx4Ykgyz/R5DGKNAMQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
builder-util-runtime "9.2.4"
|
builder-util-runtime "9.2.3"
|
||||||
fs-extra "^10.1.0"
|
fs-extra "^10.1.0"
|
||||||
js-yaml "^4.1.0"
|
js-yaml "^4.1.0"
|
||||||
lazy-val "^1.0.5"
|
lazy-val "^1.0.5"
|
||||||
|
|
|
@ -163,10 +163,6 @@ export const sidebar = [
|
||||||
text: "From Authy",
|
text: "From Authy",
|
||||||
link: "/auth/migration-guides/authy/",
|
link: "/auth/migration-guides/authy/",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: "From Steam",
|
|
||||||
link: "/auth/migration-guides/steam/",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: "Exporting your data",
|
text: "Exporting your data",
|
||||||
link: "/auth/migration-guides/export",
|
link: "/auth/migration-guides/export",
|
||||||
|
|
|
@ -7,5 +7,4 @@ description:
|
||||||
# Migrating to/from Ente Auth
|
# Migrating to/from Ente Auth
|
||||||
|
|
||||||
- [Migrating from Authy](authy/)
|
- [Migrating from Authy](authy/)
|
||||||
- [Importing codes from Steam](steam/)
|
|
||||||
- [Exporting your data out of Ente Auth](export)
|
- [Exporting your data out of Ente Auth](export)
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
---
|
|
||||||
title: Migrating from Steam Authenticator
|
|
||||||
description: Guide for importing from Steam Authenticator to Ente Auth
|
|
||||||
---
|
|
||||||
|
|
||||||
# Migrating from Steam Authenticator
|
|
||||||
|
|
||||||
A guide written by an ente.io lover
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
>
|
|
||||||
> Steam Authenticator code is only supported after auth-v3.0.3, check the app's
|
|
||||||
> version number before migration.
|
|
||||||
|
|
||||||
One way to migrate is to
|
|
||||||
[use this tool by dyc3](https://github.com/dyc3/steamguard-cli/releases/latest)
|
|
||||||
to simplify the process and skip directly to generating a qr code to Ente
|
|
||||||
Authenticator.
|
|
||||||
|
|
||||||
## Download/Install steamguard-cli
|
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
1. Download `steamguard.exe` from the [releases page][releases].
|
|
||||||
2. Place `steamguard.exe` in a folder of your choice. For this example, we will
|
|
||||||
use `%USERPROFILE%\Desktop`.
|
|
||||||
3. Open Powershell or Command Prompt. The prompt should be at `%USERPROFILE%`
|
|
||||||
(eg. `C:\Users\<username>`).
|
|
||||||
4. Use `cd` to change directory into the folder where you placed
|
|
||||||
`steamguard.exe`. For this example, it would be `cd Desktop`.
|
|
||||||
5. You should now be able to run `steamguard.exe` by typing
|
|
||||||
`.\steamguard.exe --help` and pressing enter.
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
#### Ubuntu/Debian
|
|
||||||
|
|
||||||
1. Download the `.deb` from the [releases page][releases].
|
|
||||||
2. Open a terminal and run this to install it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dpkg -i ./steamguard-cli_<version>_amd64.deb
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Other Linux
|
|
||||||
|
|
||||||
1. Download `steamguard` from the [releases page][releases]
|
|
||||||
2. Make it executable, and move `steamguard` to `/usr/local/bin` or any other
|
|
||||||
directory in your `$PATH`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
chmod +x ./steamguard
|
|
||||||
sudo mv ./steamguard /usr/local/bin
|
|
||||||
```
|
|
||||||
|
|
||||||
3. You should now be able to run `steamguard` by typing `steamguard --help` and
|
|
||||||
pressing enter.
|
|
||||||
|
|
||||||
## Login to Steam account
|
|
||||||
|
|
||||||
Set up a new account with steamguard-cli
|
|
||||||
|
|
||||||
```bash
|
|
||||||
steamguard setup # set up a new account with steamguard-cli
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generate & importing QR codes
|
|
||||||
|
|
||||||
steamguard-cli can then generate a QR code for your 2FA secret.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
steamguard qr # print QR code for the first account in your maFiles
|
|
||||||
steamguard -u <account name> qr # print QR code for a specific account
|
|
||||||
```
|
|
||||||
|
|
||||||
Open Ente Auth, press the '+' button, select `Scan a QR code`, and scan the qr
|
|
||||||
code.
|
|
||||||
|
|
||||||
You should now have your steam code inside Ente Auth
|
|
|
@ -78,23 +78,3 @@ To summarize:
|
||||||
Set the S3 bucket `endpoint` in `credentials.yaml` to a `yourserverip:3200` or
|
Set the S3 bucket `endpoint` in `credentials.yaml` to a `yourserverip:3200` or
|
||||||
some such IP/hostname that accessible from both where you are running the Ente
|
some such IP/hostname that accessible from both where you are running the Ente
|
||||||
clients (e.g. the mobile app) and also from within the Docker compose cluster.
|
clients (e.g. the mobile app) and also from within the Docker compose cluster.
|
||||||
|
|
||||||
### 403 Forbidden
|
|
||||||
|
|
||||||
If museum (`2`) is able to make a network connection to your S3 bucket (`3`) but
|
|
||||||
uploads are still failing, it could be a credentials or permissions issue. A
|
|
||||||
telltale sign of this is that in the museum logs you can see `403 Forbidden`
|
|
||||||
errors about it not able to find the size of a file even though the
|
|
||||||
corresponding object exists in the S3 bucket.
|
|
||||||
|
|
||||||
To fix these, you should ensure the following:
|
|
||||||
|
|
||||||
1. The bucket CORS rules do not allow museum to access these objects.
|
|
||||||
|
|
||||||
> For uploading files from the browser, you will need to currently set
|
|
||||||
> allowedOrigins to "\*", and allow the "X-Auth-Token", "X-Client-Package"
|
|
||||||
> headers configuration too.
|
|
||||||
> [Here is an example of a working configuration](https://github.com/ente-io/ente/discussions/1764#discussioncomment-9478204).
|
|
||||||
|
|
||||||
2. The credentials are not being picked up (you might be setting the correct
|
|
||||||
creds, but not in the place where museum picks them from).
|
|
||||||
|
|
|
@ -7,10 +7,7 @@ allprojects {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
// mavenLocal() // for FDroid
|
mavenLocal() // for FDroid
|
||||||
maven {
|
|
||||||
url "${project(':background_fetch').projectDir}/libs"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
org.gradle.jvmargs=-Xmx4608m
|
org.gradle.jvmargs=-Xmx6144m
|
||||||
android.enableR8=true
|
android.enableR8=true
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
|
|
@ -35,10 +35,10 @@ import 'package:photos/services/sync_service.dart';
|
||||||
import 'package:photos/utils/crypto_util.dart';
|
import 'package:photos/utils/crypto_util.dart';
|
||||||
import 'package:photos/utils/file_uploader.dart';
|
import 'package:photos/utils/file_uploader.dart';
|
||||||
import 'package:photos/utils/validator_util.dart';
|
import 'package:photos/utils/validator_util.dart';
|
||||||
import "package:photos/utils/wakelock_util.dart";
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import "package:tuple/tuple.dart";
|
import "package:tuple/tuple.dart";
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
Configuration._privateConstructor();
|
Configuration._privateConstructor();
|
||||||
|
@ -585,7 +585,7 @@ class Configuration {
|
||||||
|
|
||||||
Future<void> setShouldKeepDeviceAwake(bool value) async {
|
Future<void> setShouldKeepDeviceAwake(bool value) async {
|
||||||
await _preferences.setBool(keyShouldKeepDeviceAwake, value);
|
await _preferences.setBool(keyShouldKeepDeviceAwake, value);
|
||||||
await EnteWakeLock.toggle(enable: value);
|
await WakelockPlus.toggle(enable: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setShouldBackupVideos(bool value) async {
|
Future<void> setShouldBackupVideos(bool value) async {
|
||||||
|
|
|
@ -69,8 +69,6 @@ const galleryGridSpacing = 2.0;
|
||||||
|
|
||||||
const kSearchSectionLimit = 9;
|
const kSearchSectionLimit = 9;
|
||||||
|
|
||||||
const maxPickAssetLimit = 50;
|
|
||||||
|
|
||||||
const iOSGroupID = "group.io.ente.frame.SlideshowWidget";
|
const iOSGroupID = "group.io.ente.frame.SlideshowWidget";
|
||||||
|
|
||||||
const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
|
const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB'
|
||||||
|
|
|
@ -13,8 +13,6 @@ import "package:photos/face/model/face.dart";
|
||||||
import "package:photos/models/file/file.dart";
|
import "package:photos/models/file/file.dart";
|
||||||
import "package:photos/services/machine_learning/face_ml/face_clustering/face_info_for_clustering.dart";
|
import "package:photos/services/machine_learning/face_ml/face_clustering/face_info_for_clustering.dart";
|
||||||
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
|
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
|
||||||
import "package:photos/services/machine_learning/face_ml/face_ml_result.dart";
|
|
||||||
import "package:photos/utils/ml_util.dart";
|
|
||||||
import 'package:sqlite_async/sqlite_async.dart';
|
import 'package:sqlite_async/sqlite_async.dart';
|
||||||
|
|
||||||
/// Stores all data for the FacesML-related features. The database can be accessed by `FaceMLDataDB.instance.database`.
|
/// Stores all data for the FacesML-related features. The database can be accessed by `FaceMLDataDB.instance.database`.
|
||||||
|
@ -35,15 +33,6 @@ class FaceMLDataDB {
|
||||||
|
|
||||||
static final FaceMLDataDB instance = FaceMLDataDB._privateConstructor();
|
static final FaceMLDataDB instance = FaceMLDataDB._privateConstructor();
|
||||||
|
|
||||||
static final _migrationScripts = [
|
|
||||||
createFacesTable,
|
|
||||||
createFaceClustersTable,
|
|
||||||
createClusterPersonTable,
|
|
||||||
createClusterSummaryTable,
|
|
||||||
createNotPersonFeedbackTable,
|
|
||||||
fcClusterIDIndex,
|
|
||||||
];
|
|
||||||
|
|
||||||
// only have a single app-wide reference to the database
|
// only have a single app-wide reference to the database
|
||||||
static Future<SqliteDatabase>? _sqliteAsyncDBFuture;
|
static Future<SqliteDatabase>? _sqliteAsyncDBFuture;
|
||||||
|
|
||||||
|
@ -59,42 +48,17 @@ class FaceMLDataDB {
|
||||||
_logger.info("Opening sqlite_async access: DB path " + databaseDirectory);
|
_logger.info("Opening sqlite_async access: DB path " + databaseDirectory);
|
||||||
final asyncDBConnection =
|
final asyncDBConnection =
|
||||||
SqliteDatabase(path: databaseDirectory, maxReaders: 2);
|
SqliteDatabase(path: databaseDirectory, maxReaders: 2);
|
||||||
final stopwatch = Stopwatch()..start();
|
await _onCreate(asyncDBConnection);
|
||||||
_logger.info("FaceMLDataDB: Starting migration");
|
|
||||||
await _migrate(asyncDBConnection);
|
|
||||||
_logger.info(
|
|
||||||
"FaceMLDataDB Migration took ${stopwatch.elapsedMilliseconds} ms",
|
|
||||||
);
|
|
||||||
stopwatch.stop();
|
|
||||||
|
|
||||||
return asyncDBConnection;
|
return asyncDBConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrate(
|
Future<void> _onCreate(SqliteDatabase asyncDBConnection) async {
|
||||||
SqliteDatabase database,
|
await asyncDBConnection.execute(createFacesTable);
|
||||||
) async {
|
await asyncDBConnection.execute(createFaceClustersTable);
|
||||||
final result = await database.execute('PRAGMA user_version');
|
await asyncDBConnection.execute(createClusterPersonTable);
|
||||||
final currentVersion = result[0]['user_version'] as int;
|
await asyncDBConnection.execute(createClusterSummaryTable);
|
||||||
final toVersion = _migrationScripts.length;
|
await asyncDBConnection.execute(createNotPersonFeedbackTable);
|
||||||
|
await asyncDBConnection.execute(fcClusterIDIndex);
|
||||||
if (currentVersion < toVersion) {
|
|
||||||
_logger.info("Migrating database from $currentVersion to $toVersion");
|
|
||||||
await database.writeTransaction((tx) async {
|
|
||||||
for (int i = currentVersion + 1; i <= toVersion; i++) {
|
|
||||||
try {
|
|
||||||
await tx.execute(_migrationScripts[i - 1]);
|
|
||||||
} catch (e) {
|
|
||||||
_logger.severe("Error running migration script index ${i - 1}", e);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await tx.execute('PRAGMA user_version = $toVersion');
|
|
||||||
});
|
|
||||||
} else if (currentVersion > toVersion) {
|
|
||||||
throw AssertionError(
|
|
||||||
"currentVersion($currentVersion) cannot be greater than toVersion($toVersion)",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// bulkInsertFaces inserts the faces in the database in batches of 1000.
|
// bulkInsertFaces inserts the faces in the database in batches of 1000.
|
||||||
|
@ -229,10 +193,10 @@ class FaceMLDataDB {
|
||||||
final db = await instance.asyncDB;
|
final db = await instance.asyncDB;
|
||||||
|
|
||||||
await db.execute(deleteFacesTable);
|
await db.execute(deleteFacesTable);
|
||||||
await db.execute(deleteFaceClustersTable);
|
await db.execute(dropClusterPersonTable);
|
||||||
await db.execute(deleteClusterPersonTable);
|
await db.execute(dropClusterSummaryTable);
|
||||||
await db.execute(deleteClusterSummaryTable);
|
await db.execute(deletePersonTable);
|
||||||
await db.execute(deleteNotPersonFeedbackTable);
|
await db.execute(dropNotPersonFeedbackTable);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Iterable<Uint8List>> getFaceEmbeddingsForCluster(
|
Future<Iterable<Uint8List>> getFaceEmbeddingsForCluster(
|
||||||
|
@ -285,7 +249,7 @@ class FaceMLDataDB {
|
||||||
final List<int> fileId = [recentFileID];
|
final List<int> fileId = [recentFileID];
|
||||||
int? avatarFileId;
|
int? avatarFileId;
|
||||||
if (avatarFaceId != null) {
|
if (avatarFaceId != null) {
|
||||||
avatarFileId = tryGetFileIdFromFaceId(avatarFaceId);
|
avatarFileId = int.tryParse(avatarFaceId.split('_')[0]);
|
||||||
if (avatarFileId != null) {
|
if (avatarFileId != null) {
|
||||||
fileId.add(avatarFileId);
|
fileId.add(avatarFileId);
|
||||||
}
|
}
|
||||||
|
@ -437,10 +401,8 @@ class FaceMLDataDB {
|
||||||
final personID = map[personIdColumn] as String;
|
final personID = map[personIdColumn] as String;
|
||||||
final clusterID = map[fcClusterID] as int;
|
final clusterID = map[fcClusterID] as int;
|
||||||
final faceID = map[fcFaceId] as String;
|
final faceID = map[fcFaceId] as String;
|
||||||
result
|
result.putIfAbsent(personID, () => {}).putIfAbsent(clusterID, () => {})
|
||||||
.putIfAbsent(personID, () => {})
|
.add(faceID);
|
||||||
.putIfAbsent(clusterID, () => {})
|
|
||||||
.add(faceID);
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -514,7 +476,8 @@ class FaceMLDataDB {
|
||||||
for (final map in maps) {
|
for (final map in maps) {
|
||||||
final clusterID = map[fcClusterID] as int;
|
final clusterID = map[fcClusterID] as int;
|
||||||
final faceID = map[fcFaceId] as String;
|
final faceID = map[fcFaceId] as String;
|
||||||
final fileID = getFileIdFromFaceId(faceID);
|
final x = faceID.split('_').first;
|
||||||
|
final fileID = int.parse(x);
|
||||||
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -702,55 +665,19 @@ class FaceMLDataDB {
|
||||||
return maps.first['count'] as int;
|
return maps.first['count'] as int;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getClusteredOrFacelessFileCount() async {
|
Future<int> getClusteredFaceCount() async {
|
||||||
final db = await instance.asyncDB;
|
final db = await instance.asyncDB;
|
||||||
final List<Map<String, dynamic>> clustered = await db.getAll(
|
final List<Map<String, dynamic>> maps = await db.getAll(
|
||||||
'SELECT $fcFaceId FROM $faceClustersTable',
|
'SELECT COUNT(DISTINCT $fcFaceId) as count FROM $faceClustersTable',
|
||||||
);
|
);
|
||||||
final Set<int> clusteredFileIDs = {};
|
return maps.first['count'] as int;
|
||||||
for (final map in clustered) {
|
|
||||||
final int fileID = getFileIdFromFaceId(map[fcFaceId] as String);
|
|
||||||
clusteredFileIDs.add(fileID);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> badFacesFiles = await db.getAll(
|
|
||||||
'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore <= $kMinimumQualityFaceScore OR $faceBlur <= $kLaplacianHardThreshold',
|
|
||||||
);
|
|
||||||
final Set<int> badFileIDs = {};
|
|
||||||
for (final map in badFacesFiles) {
|
|
||||||
badFileIDs.add(map[fileIDColumn] as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> goodFacesFiles = await db.getAll(
|
|
||||||
'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore > $kMinimumQualityFaceScore AND $faceBlur > $kLaplacianHardThreshold',
|
|
||||||
);
|
|
||||||
final Set<int> goodFileIDs = {};
|
|
||||||
for (final map in goodFacesFiles) {
|
|
||||||
goodFileIDs.add(map[fileIDColumn] as int);
|
|
||||||
}
|
|
||||||
final trulyFacelessFiles = badFileIDs.difference(goodFileIDs);
|
|
||||||
return clusteredFileIDs.length + trulyFacelessFiles.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<double> getClusteredToIndexableFilesRatio() async {
|
Future<double> getClusteredToTotalFacesRatio() async {
|
||||||
final int indexableFiles = (await getIndexableFileIDs()).length;
|
final int totalFaces = await getTotalFaceCount();
|
||||||
final int clusteredFiles = await getClusteredOrFacelessFileCount();
|
final int clusteredFaces = await getClusteredFaceCount();
|
||||||
|
|
||||||
return clusteredFiles / indexableFiles;
|
return clusteredFaces / totalFaces;
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getUnclusteredFaceCount() async {
|
|
||||||
final db = await instance.asyncDB;
|
|
||||||
const String query = '''
|
|
||||||
SELECT f.$faceIDColumn
|
|
||||||
FROM $facesTable f
|
|
||||||
LEFT JOIN $faceClustersTable fc ON f.$faceIDColumn = fc.$fcFaceId
|
|
||||||
WHERE f.$faceScore > $kMinimumQualityFaceScore
|
|
||||||
AND f.$faceBlur > $kLaplacianHardThreshold
|
|
||||||
AND fc.$fcFaceId IS NULL
|
|
||||||
''';
|
|
||||||
final List<Map<String, dynamic>> maps = await db.getAll(query);
|
|
||||||
return maps.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getBlurryFaceCount([
|
Future<int> getBlurryFaceCount([
|
||||||
|
@ -768,7 +695,7 @@ class FaceMLDataDB {
|
||||||
try {
|
try {
|
||||||
final db = await instance.asyncDB;
|
final db = await instance.asyncDB;
|
||||||
|
|
||||||
await db.execute(deleteFaceClustersTable);
|
await db.execute(dropFaceClustersTable);
|
||||||
await db.execute(createFaceClustersTable);
|
await db.execute(createFaceClustersTable);
|
||||||
await db.execute(fcClusterIDIndex);
|
await db.execute(fcClusterIDIndex);
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
|
@ -868,7 +795,7 @@ class FaceMLDataDB {
|
||||||
for (final map in maps) {
|
for (final map in maps) {
|
||||||
final clusterID = map[clusterIDColumn] as int;
|
final clusterID = map[clusterIDColumn] as int;
|
||||||
final String faceID = map[fcFaceId] as String;
|
final String faceID = map[fcFaceId] as String;
|
||||||
final fileID = getFileIdFromFaceId(faceID);
|
final fileID = int.parse(faceID.split('_').first);
|
||||||
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -887,8 +814,8 @@ class FaceMLDataDB {
|
||||||
final Map<int, Set<int>> result = {};
|
final Map<int, Set<int>> result = {};
|
||||||
for (final map in maps) {
|
for (final map in maps) {
|
||||||
final clusterID = map[fcClusterID] as int;
|
final clusterID = map[fcClusterID] as int;
|
||||||
final faceID = map[fcFaceId] as String;
|
final faceId = map[fcFaceId] as String;
|
||||||
final fileID = getFileIdFromFaceId(faceID);
|
final fileID = int.parse(faceId.split("_").first);
|
||||||
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
result[fileID] = (result[fileID] ?? {})..add(clusterID);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -979,15 +906,16 @@ class FaceMLDataDB {
|
||||||
if (faces) {
|
if (faces) {
|
||||||
await db.execute(deleteFacesTable);
|
await db.execute(deleteFacesTable);
|
||||||
await db.execute(createFacesTable);
|
await db.execute(createFacesTable);
|
||||||
await db.execute(deleteFaceClustersTable);
|
await db.execute(dropFaceClustersTable);
|
||||||
await db.execute(createFaceClustersTable);
|
await db.execute(createFaceClustersTable);
|
||||||
await db.execute(fcClusterIDIndex);
|
await db.execute(fcClusterIDIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.execute(deleteClusterPersonTable);
|
await db.execute(deletePersonTable);
|
||||||
await db.execute(deleteNotPersonFeedbackTable);
|
await db.execute(dropClusterPersonTable);
|
||||||
await db.execute(deleteClusterSummaryTable);
|
await db.execute(dropNotPersonFeedbackTable);
|
||||||
await db.execute(deleteFaceClustersTable);
|
await db.execute(dropClusterSummaryTable);
|
||||||
|
await db.execute(dropFaceClustersTable);
|
||||||
|
|
||||||
await db.execute(createClusterPersonTable);
|
await db.execute(createClusterPersonTable);
|
||||||
await db.execute(createNotPersonFeedbackTable);
|
await db.execute(createNotPersonFeedbackTable);
|
||||||
|
@ -1005,8 +933,9 @@ class FaceMLDataDB {
|
||||||
final db = await instance.asyncDB;
|
final db = await instance.asyncDB;
|
||||||
|
|
||||||
// Drop the tables
|
// Drop the tables
|
||||||
await db.execute(deleteClusterPersonTable);
|
await db.execute(deletePersonTable);
|
||||||
await db.execute(deleteNotPersonFeedbackTable);
|
await db.execute(dropClusterPersonTable);
|
||||||
|
await db.execute(dropNotPersonFeedbackTable);
|
||||||
|
|
||||||
// Recreate the tables
|
// Recreate the tables
|
||||||
await db.execute(createClusterPersonTable);
|
await db.execute(createClusterPersonTable);
|
||||||
|
@ -1035,7 +964,7 @@ class FaceMLDataDB {
|
||||||
final Map<String, int> faceIDToClusterID = {};
|
final Map<String, int> faceIDToClusterID = {};
|
||||||
for (final row in faceIdsResult) {
|
for (final row in faceIdsResult) {
|
||||||
final faceID = row[fcFaceId] as String;
|
final faceID = row[fcFaceId] as String;
|
||||||
if (fileIds.contains(getFileIdFromFaceId(faceID))) {
|
if (fileIds.contains(faceID.split('_').first)) {
|
||||||
maxClusterID += 1;
|
maxClusterID += 1;
|
||||||
faceIDToClusterID[faceID] = maxClusterID;
|
faceIDToClusterID[faceID] = maxClusterID;
|
||||||
}
|
}
|
||||||
|
@ -1061,7 +990,7 @@ class FaceMLDataDB {
|
||||||
final Map<String, int> faceIDToClusterID = {};
|
final Map<String, int> faceIDToClusterID = {};
|
||||||
for (final row in faceIdsResult) {
|
for (final row in faceIdsResult) {
|
||||||
final faceID = row[fcFaceId] as String;
|
final faceID = row[fcFaceId] as String;
|
||||||
if (fileIds.contains(getFileIdFromFaceId(faceID))) {
|
if (fileIds.contains(faceID.split('_').first)) {
|
||||||
maxClusterID += 1;
|
maxClusterID += 1;
|
||||||
faceIDToClusterID[faceID] = maxClusterID;
|
faceIDToClusterID[faceID] = maxClusterID;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const createFacesTable = '''CREATE TABLE IF NOT EXISTS $facesTable (
|
||||||
);
|
);
|
||||||
''';
|
''';
|
||||||
|
|
||||||
const deleteFacesTable = 'DELETE FROM $facesTable';
|
const deleteFacesTable = 'DROP TABLE IF EXISTS $facesTable';
|
||||||
// End of Faces Table Fields & Schema Queries
|
// End of Faces Table Fields & Schema Queries
|
||||||
|
|
||||||
//##region Face Clusters Table Fields & Schema Queries
|
//##region Face Clusters Table Fields & Schema Queries
|
||||||
|
@ -48,9 +48,15 @@ CREATE TABLE IF NOT EXISTS $faceClustersTable (
|
||||||
// -- Creating a non-unique index on clusterID for query optimization
|
// -- Creating a non-unique index on clusterID for query optimization
|
||||||
const fcClusterIDIndex =
|
const fcClusterIDIndex =
|
||||||
'''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);''';
|
'''CREATE INDEX IF NOT EXISTS idx_fcClusterID ON $faceClustersTable($fcClusterID);''';
|
||||||
const deleteFaceClustersTable = 'DELETE FROM $faceClustersTable';
|
const dropFaceClustersTable = 'DROP TABLE IF EXISTS $faceClustersTable';
|
||||||
//##endregion
|
//##endregion
|
||||||
|
|
||||||
|
// People Table Fields & Schema Queries
|
||||||
|
const personTable = 'person';
|
||||||
|
|
||||||
|
const deletePersonTable = 'DROP TABLE IF EXISTS $personTable';
|
||||||
|
//End People Table Fields & Schema Queries
|
||||||
|
|
||||||
// Clusters Table Fields & Schema Queries
|
// Clusters Table Fields & Schema Queries
|
||||||
const clusterPersonTable = 'cluster_person';
|
const clusterPersonTable = 'cluster_person';
|
||||||
const personIdColumn = 'person_id';
|
const personIdColumn = 'person_id';
|
||||||
|
@ -63,7 +69,7 @@ CREATE TABLE IF NOT EXISTS $clusterPersonTable (
|
||||||
PRIMARY KEY($personIdColumn, $clusterIDColumn)
|
PRIMARY KEY($personIdColumn, $clusterIDColumn)
|
||||||
);
|
);
|
||||||
''';
|
''';
|
||||||
const deleteClusterPersonTable = 'DELETE FROM $clusterPersonTable';
|
const dropClusterPersonTable = 'DROP TABLE IF EXISTS $clusterPersonTable';
|
||||||
// End Clusters Table Fields & Schema Queries
|
// End Clusters Table Fields & Schema Queries
|
||||||
|
|
||||||
/// Cluster Summary Table Fields & Schema Queries
|
/// Cluster Summary Table Fields & Schema Queries
|
||||||
|
@ -79,7 +85,7 @@ CREATE TABLE IF NOT EXISTS $clusterSummaryTable (
|
||||||
);
|
);
|
||||||
''';
|
''';
|
||||||
|
|
||||||
const deleteClusterSummaryTable = 'DELETE FROM $clusterSummaryTable';
|
const dropClusterSummaryTable = 'DROP TABLE IF EXISTS $clusterSummaryTable';
|
||||||
|
|
||||||
/// End Cluster Summary Table Fields & Schema Queries
|
/// End Cluster Summary Table Fields & Schema Queries
|
||||||
|
|
||||||
|
@ -93,5 +99,5 @@ CREATE TABLE IF NOT EXISTS $notPersonFeedback (
|
||||||
PRIMARY KEY($personIdColumn, $clusterIDColumn)
|
PRIMARY KEY($personIdColumn, $clusterIDColumn)
|
||||||
);
|
);
|
||||||
''';
|
''';
|
||||||
const deleteNotPersonFeedbackTable = 'DELETE FROM $notPersonFeedback';
|
const dropNotPersonFeedbackTable = 'DROP TABLE IF EXISTS $notPersonFeedback';
|
||||||
// End Clusters Table Fields & Schema Queries
|
// End Clusters Table Fields & Schema Queries
|
||||||
|
|
2
mobile/lib/generated/intl/messages_cs.dart
generated
|
@ -54,8 +54,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
|
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
|
||||||
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
||||||
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
||||||
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
|
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
|
||||||
"longPressAnEmailToVerifyEndToEndEncryption":
|
"longPressAnEmailToVerifyEndToEndEncryption":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_de.dart
generated
|
@ -819,8 +819,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Falscher Wiederherstellungs-Schlüssel"),
|
"Falscher Wiederherstellungs-Schlüssel"),
|
||||||
"indexedItems":
|
"indexedItems":
|
||||||
MessageLookupByLibrary.simpleMessage("Indizierte Elemente"),
|
MessageLookupByLibrary.simpleMessage("Indizierte Elemente"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"),
|
MessageLookupByLibrary.simpleMessage("Unsicheres Gerät"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_en.dart
generated
|
@ -813,8 +813,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"incorrectRecoveryKeyTitle":
|
"incorrectRecoveryKeyTitle":
|
||||||
MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
|
MessageLookupByLibrary.simpleMessage("Incorrect recovery key"),
|
||||||
"indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
|
"indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused. It will automatically resume when device is ready."),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Insecure device"),
|
MessageLookupByLibrary.simpleMessage("Insecure device"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_es.dart
generated
|
@ -699,8 +699,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"La clave de recuperación introducida es incorrecta"),
|
"La clave de recuperación introducida es incorrecta"),
|
||||||
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
||||||
"Clave de recuperación incorrecta"),
|
"Clave de recuperación incorrecta"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"),
|
MessageLookupByLibrary.simpleMessage("Dispositivo inseguro"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_fr.dart
generated
|
@ -804,8 +804,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"La clé de secours que vous avez entrée est incorrecte"),
|
"La clé de secours que vous avez entrée est incorrecte"),
|
||||||
"incorrectRecoveryKeyTitle":
|
"incorrectRecoveryKeyTitle":
|
||||||
MessageLookupByLibrary.simpleMessage("Clé de secours non valide"),
|
MessageLookupByLibrary.simpleMessage("Clé de secours non valide"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Appareil non sécurisé"),
|
MessageLookupByLibrary.simpleMessage("Appareil non sécurisé"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_it.dart
generated
|
@ -773,8 +773,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Il codice che hai inserito non è corretto"),
|
"Il codice che hai inserito non è corretto"),
|
||||||
"incorrectRecoveryKeyTitle":
|
"incorrectRecoveryKeyTitle":
|
||||||
MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"),
|
MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Dispositivo non sicuro"),
|
MessageLookupByLibrary.simpleMessage("Dispositivo non sicuro"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_ko.dart
generated
|
@ -54,8 +54,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
|
"Please note that this will result in a higher bandwidth and battery usage until all items are indexed."),
|
||||||
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
||||||
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
||||||
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
|
"locations": MessageLookupByLibrary.simpleMessage("Locations"),
|
||||||
"longPressAnEmailToVerifyEndToEndEncryption":
|
"longPressAnEmailToVerifyEndToEndEncryption":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_nl.dart
generated
|
@ -840,8 +840,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"),
|
MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"),
|
||||||
"indexedItems":
|
"indexedItems":
|
||||||
MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"),
|
MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Onveilig apparaat"),
|
MessageLookupByLibrary.simpleMessage("Onveilig apparaat"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_no.dart
generated
|
@ -72,8 +72,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
|
"feedback": MessageLookupByLibrary.simpleMessage("Tilbakemelding"),
|
||||||
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
|
||||||
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
"foundFaces": MessageLookupByLibrary.simpleMessage("Found faces"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"invalidEmailAddress":
|
"invalidEmailAddress":
|
||||||
MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"),
|
MessageLookupByLibrary.simpleMessage("Ugyldig e-postadresse"),
|
||||||
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
||||||
|
|
2
mobile/lib/generated/intl/messages_pl.dart
generated
|
@ -131,8 +131,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"),
|
MessageLookupByLibrary.simpleMessage("Kod jest nieprawidłowy"),
|
||||||
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
||||||
"Nieprawidłowy klucz odzyskiwania"),
|
"Nieprawidłowy klucz odzyskiwania"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"invalidEmailAddress":
|
"invalidEmailAddress":
|
||||||
MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"),
|
MessageLookupByLibrary.simpleMessage("Nieprawidłowy adres e-mail"),
|
||||||
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
"joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"),
|
||||||
|
|
2
mobile/lib/generated/intl/messages_pt.dart
generated
|
@ -836,8 +836,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
"incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage(
|
||||||
"Chave de recuperação incorreta"),
|
"Chave de recuperação incorreta"),
|
||||||
"indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"),
|
"indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice":
|
"insecureDevice":
|
||||||
MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"),
|
MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"),
|
||||||
"installManually":
|
"installManually":
|
||||||
|
|
2
mobile/lib/generated/intl/messages_zh.dart
generated
|
@ -686,8 +686,6 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||||
"incorrectRecoveryKeyTitle":
|
"incorrectRecoveryKeyTitle":
|
||||||
MessageLookupByLibrary.simpleMessage("不正确的恢复密钥"),
|
MessageLookupByLibrary.simpleMessage("不正确的恢复密钥"),
|
||||||
"indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"),
|
"indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"),
|
||||||
"indexingIsPaused": MessageLookupByLibrary.simpleMessage(
|
|
||||||
"Indexing is paused, will automatically resume when device is ready"),
|
|
||||||
"insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"),
|
"insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"),
|
||||||
"installManually": MessageLookupByLibrary.simpleMessage("手动安装"),
|
"installManually": MessageLookupByLibrary.simpleMessage("手动安装"),
|
||||||
"invalidEmailAddress":
|
"invalidEmailAddress":
|
||||||
|
|
10
mobile/lib/generated/l10n.dart
generated
|
@ -8793,16 +8793,6 @@ class S {
|
||||||
args: [],
|
args: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Indexing is paused. It will automatically resume when device is ready.`
|
|
||||||
String get indexingIsPaused {
|
|
||||||
return Intl.message(
|
|
||||||
'Indexing is paused. It will automatically resume when device is ready.',
|
|
||||||
name: 'indexingIsPaused',
|
|
||||||
desc: '',
|
|
||||||
args: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
class AppLocalizationDelegate extends LocalizationsDelegate<S> {
|
||||||
|
|
|
@ -24,6 +24,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1212,6 +1212,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1235,6 +1235,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused. It will automatically resume when device is ready."
|
}
|
||||||
}
|
|
|
@ -986,6 +986,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1167,6 +1167,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1129,6 +1129,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -24,6 +24,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1230,6 +1230,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -38,6 +38,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -125,6 +125,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1235,6 +1235,5 @@
|
||||||
"faceRecognition": "Reconhecimento facial",
|
"faceRecognition": "Reconhecimento facial",
|
||||||
"faceRecognitionIndexingDescription": "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados.",
|
"faceRecognitionIndexingDescription": "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados.",
|
||||||
"foundFaces": "Rostos encontrados",
|
"foundFaces": "Rostos encontrados",
|
||||||
"clusteringProgress": "Progresso de agrupamento",
|
"clusteringProgress": "Progresso de agrupamento"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -1230,6 +1230,5 @@
|
||||||
"faceRecognition": "Face recognition",
|
"faceRecognition": "Face recognition",
|
||||||
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
"faceRecognitionIndexingDescription": "Please note that this will result in a higher bandwidth and battery usage until all items are indexed.",
|
||||||
"foundFaces": "Found faces",
|
"foundFaces": "Found faces",
|
||||||
"clusteringProgress": "Clustering progress",
|
"clusteringProgress": "Clustering progress"
|
||||||
"indexingIsPaused": "Indexing is paused, will automatically resume when device is ready"
|
|
||||||
}
|
}
|
|
@ -5,7 +5,6 @@ import "dart:isolate";
|
||||||
import "package:adaptive_theme/adaptive_theme.dart";
|
import "package:adaptive_theme/adaptive_theme.dart";
|
||||||
import 'package:background_fetch/background_fetch.dart';
|
import 'package:background_fetch/background_fetch.dart';
|
||||||
import "package:computer/computer.dart";
|
import "package:computer/computer.dart";
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import "package:flutter/rendering.dart";
|
import "package:flutter/rendering.dart";
|
||||||
|
@ -39,7 +38,6 @@ import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.d
|
||||||
import "package:photos/services/machine_learning/machine_learning_controller.dart";
|
import "package:photos/services/machine_learning/machine_learning_controller.dart";
|
||||||
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
||||||
import 'package:photos/services/memories_service.dart';
|
import 'package:photos/services/memories_service.dart';
|
||||||
import 'package:photos/services/push_service.dart';
|
|
||||||
import 'package:photos/services/remote_sync_service.dart';
|
import 'package:photos/services/remote_sync_service.dart';
|
||||||
import 'package:photos/services/search_service.dart';
|
import 'package:photos/services/search_service.dart';
|
||||||
import "package:photos/services/storage_bonus_service.dart";
|
import "package:photos/services/storage_bonus_service.dart";
|
||||||
|
@ -51,7 +49,6 @@ import 'package:photos/services/user_service.dart';
|
||||||
import 'package:photos/ui/tools/app_lock.dart';
|
import 'package:photos/ui/tools/app_lock.dart';
|
||||||
import 'package:photos/ui/tools/lock_screen.dart';
|
import 'package:photos/ui/tools/lock_screen.dart';
|
||||||
import 'package:photos/utils/crypto_util.dart';
|
import 'package:photos/utils/crypto_util.dart';
|
||||||
import "package:photos/utils/email_util.dart";
|
|
||||||
import 'package:photos/utils/file_uploader.dart';
|
import 'package:photos/utils/file_uploader.dart';
|
||||||
import 'package:photos/utils/local_settings.dart';
|
import 'package:photos/utils/local_settings.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -181,16 +178,6 @@ void _headlessTaskHandler(HeadlessTask task) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _init(bool isBackground, {String via = ''}) async {
|
Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||||
bool initComplete = false;
|
|
||||||
Future.delayed(const Duration(seconds: 15), () {
|
|
||||||
if (!initComplete && !isBackground) {
|
|
||||||
sendLogsForInit(
|
|
||||||
"support@ente.io",
|
|
||||||
"Stuck on splash screen for >= 15 seconds",
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_isProcessRunning = true;
|
_isProcessRunning = true;
|
||||||
_logger.info("Initializing... inBG =$isBackground via: $via");
|
_logger.info("Initializing... inBG =$isBackground via: $via");
|
||||||
final SharedPreferences preferences = await SharedPreferences.getInstance();
|
final SharedPreferences preferences = await SharedPreferences.getInstance();
|
||||||
|
@ -234,23 +221,19 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||||
await HomeWidgetService.instance.countHomeWidgets() == 0) {
|
await HomeWidgetService.instance.countHomeWidgets() == 0) {
|
||||||
unawaited(HomeWidgetService.instance.initHomeWidget());
|
unawaited(HomeWidgetService.instance.initHomeWidget());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
PushService.instance.init().then((_) {
|
|
||||||
FirebaseMessaging.onBackgroundMessage(
|
|
||||||
_firebaseMessagingBackgroundHandler,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
unawaited(SemanticSearchService.instance.init());
|
unawaited(SemanticSearchService.instance.init());
|
||||||
MachineLearningController.instance.init();
|
MachineLearningController.instance.init();
|
||||||
if (flagService.faceSearchEnabled) {
|
// Can not including existing tf/ml binaries as they are not being built
|
||||||
unawaited(FaceMlService.instance.init());
|
// from source.
|
||||||
} else {
|
// See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819
|
||||||
if (LocalSettings.instance.isFaceIndexingEnabled) {
|
if (!UpdateService.instance.isFdroidFlavor()) {
|
||||||
unawaited(LocalSettings.instance.toggleFaceIndexing());
|
// unawaited(ObjectDetectionService.instance.init());
|
||||||
|
if (flagService.faceSearchEnabled) {
|
||||||
|
unawaited(FaceMlService.instance.init());
|
||||||
|
} else {
|
||||||
|
if (LocalSettings.instance.isFaceIndexingEnabled) {
|
||||||
|
unawaited(LocalSettings.instance.toggleFaceIndexing());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PersonService.init(
|
PersonService.init(
|
||||||
|
@ -259,7 +242,6 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
|
||||||
preferences,
|
preferences,
|
||||||
);
|
);
|
||||||
|
|
||||||
initComplete = true;
|
|
||||||
_logger.info("Initialization done");
|
_logger.info("Initialization done");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,35 +347,6 @@ Future<void> _killBGTask([String? taskId]) async {
|
||||||
Isolate.current.kill();
|
Isolate.current.kill();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
||||||
final bool isRunningInFG = await _isRunningInForeground(); // hb
|
|
||||||
final bool isInForeground = AppLifecycleService.instance.isForeground;
|
|
||||||
if (_isProcessRunning) {
|
|
||||||
_logger.info(
|
|
||||||
"Background push received when app is alive and runningInFS: $isRunningInFG inForeground: $isInForeground",
|
|
||||||
);
|
|
||||||
if (PushService.shouldSync(message)) {
|
|
||||||
await _sync('firebaseBgSyncActiveProcess');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// App is dead
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
_runWithLogs(
|
|
||||||
() async {
|
|
||||||
_logger.info("Background push received");
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
_scheduleSuicide(kBGPushTimeout); // To prevent OS from punishing us
|
|
||||||
}
|
|
||||||
await _init(true, via: 'firebasePush');
|
|
||||||
if (PushService.shouldSync(message)) {
|
|
||||||
await _sync('firebaseBgSyncNoActiveProcess');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prefix: "[fbg]",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _logFGHeartBeatInfo() async {
|
Future<void> _logFGHeartBeatInfo() async {
|
||||||
final bool isRunningInFG = await _isRunningInForeground();
|
final bool isRunningInFG = await _isRunningInForeground();
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
// import 'package:flutter/foundation.dart';
|
// import 'package:flutter/foundation.dart';
|
||||||
// import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
|
// import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart';
|
||||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:photos/core/errors.dart';
|
import 'package:photos/core/errors.dart';
|
||||||
import 'package:photos/core/network/network.dart';
|
import 'package:photos/core/network/network.dart';
|
||||||
|
@ -39,6 +38,7 @@ class BillingService {
|
||||||
final _logger = Logger("BillingService");
|
final _logger = Logger("BillingService");
|
||||||
final _enteDio = NetworkClient.instance.enteDio;
|
final _enteDio = NetworkClient.instance.enteDio;
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
bool _isOnSubscriptionPage = false;
|
bool _isOnSubscriptionPage = false;
|
||||||
|
|
||||||
Future<BillingPlans>? _future;
|
Future<BillingPlans>? _future;
|
||||||
|
@ -48,23 +48,6 @@ class BillingService {
|
||||||
// await FlutterInappPurchase.instance.initConnection;
|
// await FlutterInappPurchase.instance.initConnection;
|
||||||
// FlutterInappPurchase.instance.clearTransactionIOS();
|
// FlutterInappPurchase.instance.clearTransactionIOS();
|
||||||
// }
|
// }
|
||||||
InAppPurchase.instance.purchaseStream.listen((purchases) {
|
|
||||||
if (_isOnSubscriptionPage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (final purchase in purchases) {
|
|
||||||
if (purchase.status == PurchaseStatus.purchased) {
|
|
||||||
verifySubscription(
|
|
||||||
purchase.productID,
|
|
||||||
purchase.verificationData.serverVerificationData,
|
|
||||||
).then((response) {
|
|
||||||
InAppPurchase.instance.completePurchase(purchase);
|
|
||||||
});
|
|
||||||
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
|
|
||||||
InAppPurchase.instance.completePurchase(purchase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearCache() {
|
void clearCache() {
|
||||||
|
|
|
@ -498,8 +498,19 @@ class FaceClusteringService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the faceInfos based on fileCreationTime, in ascending order, so oldest faces are first
|
||||||
if (fileIDToCreationTime != null) {
|
if (fileIDToCreationTime != null) {
|
||||||
_sortFaceInfosOnCreationTime(faceInfos);
|
faceInfos.sort((a, b) {
|
||||||
|
if (a.fileCreationTime == null && b.fileCreationTime == null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.fileCreationTime == null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.fileCreationTime == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.fileCreationTime!.compareTo(b.fileCreationTime!);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the faceInfos such that the ones with null clusterId are at the end
|
// Sort the faceInfos such that the ones with null clusterId are at the end
|
||||||
|
@ -785,8 +796,19 @@ class FaceClusteringService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the faceInfos based on fileCreationTime, in ascending order, so oldest faces are first
|
||||||
if (fileIDToCreationTime != null) {
|
if (fileIDToCreationTime != null) {
|
||||||
_sortFaceInfosOnCreationTime(faceInfos);
|
faceInfos.sort((a, b) {
|
||||||
|
if (a.fileCreationTime == null && b.fileCreationTime == null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.fileCreationTime == null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.fileCreationTime == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.fileCreationTime!.compareTo(b.fileCreationTime!);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (faceInfos.isEmpty) {
|
if (faceInfos.isEmpty) {
|
||||||
|
@ -974,8 +996,19 @@ class FaceClusteringService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort the faceInfos based on fileCreationTime, in ascending order, so oldest faces are first
|
||||||
if (fileIDToCreationTime != null) {
|
if (fileIDToCreationTime != null) {
|
||||||
_sortFaceInfosOnCreationTime(faceInfos);
|
faceInfos.sort((a, b) {
|
||||||
|
if (a.fileCreationTime == null && b.fileCreationTime == null) {
|
||||||
|
return 0;
|
||||||
|
} else if (a.fileCreationTime == null) {
|
||||||
|
return 1;
|
||||||
|
} else if (b.fileCreationTime == null) {
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
return a.fileCreationTime!.compareTo(b.fileCreationTime!);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the embeddings
|
// Get the embeddings
|
||||||
|
@ -994,20 +1027,3 @@ class FaceClusteringService {
|
||||||
return clusteredFaceIDs;
|
return clusteredFaceIDs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort the faceInfos based on fileCreationTime, in descending order, so newest faces are first
|
|
||||||
void _sortFaceInfosOnCreationTime(
|
|
||||||
List<FaceInfo> faceInfos,
|
|
||||||
) {
|
|
||||||
faceInfos.sort((b, a) {
|
|
||||||
if (a.fileCreationTime == null && b.fileCreationTime == null) {
|
|
||||||
return 0;
|
|
||||||
} else if (a.fileCreationTime == null) {
|
|
||||||
return 1;
|
|
||||||
} else if (b.fileCreationTime == null) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return a.fileCreationTime!.compareTo(b.fileCreationTime!);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,18 +8,6 @@ class GeneralFaceMlException implements Exception {
|
||||||
String toString() => 'GeneralFaceMlException: $message';
|
String toString() => 'GeneralFaceMlException: $message';
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThumbnailRetrievalException implements Exception {
|
|
||||||
final String message;
|
|
||||||
final StackTrace stackTrace;
|
|
||||||
|
|
||||||
ThumbnailRetrievalException(this.message, this.stackTrace);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'ThumbnailRetrievalException: $message\n$stackTrace';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CouldNotRetrieveAnyFileData implements Exception {}
|
class CouldNotRetrieveAnyFileData implements Exception {}
|
||||||
|
|
||||||
class CouldNotInitializeFaceDetector implements Exception {}
|
class CouldNotInitializeFaceDetector implements Exception {}
|
||||||
|
|
|
@ -310,9 +310,5 @@ class FaceResultBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
int getFileIdFromFaceId(String faceId) {
|
int getFileIdFromFaceId(String faceId) {
|
||||||
return int.parse(faceId.split("_").first);
|
return int.parse(faceId.split("_")[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? tryGetFileIdFromFaceId(String faceId) {
|
|
||||||
return int.tryParse(faceId.split("_").first);
|
|
||||||
}
|
|
|
@ -9,10 +9,10 @@ import "dart:ui" show Image;
|
||||||
import "package:computer/computer.dart";
|
import "package:computer/computer.dart";
|
||||||
import "package:dart_ui_isolate/dart_ui_isolate.dart";
|
import "package:dart_ui_isolate/dart_ui_isolate.dart";
|
||||||
import "package:flutter/foundation.dart" show debugPrint, kDebugMode;
|
import "package:flutter/foundation.dart" show debugPrint, kDebugMode;
|
||||||
import "package:flutter/services.dart";
|
|
||||||
import "package:logging/logging.dart";
|
import "package:logging/logging.dart";
|
||||||
import "package:onnxruntime/onnxruntime.dart";
|
import "package:onnxruntime/onnxruntime.dart";
|
||||||
import "package:package_info_plus/package_info_plus.dart";
|
import "package:package_info_plus/package_info_plus.dart";
|
||||||
|
import "package:photos/core/configuration.dart";
|
||||||
import "package:photos/core/event_bus.dart";
|
import "package:photos/core/event_bus.dart";
|
||||||
import "package:photos/db/files_db.dart";
|
import "package:photos/db/files_db.dart";
|
||||||
import "package:photos/events/diff_sync_complete_event.dart";
|
import "package:photos/events/diff_sync_complete_event.dart";
|
||||||
|
@ -43,7 +43,6 @@ import 'package:photos/services/machine_learning/face_ml/face_ml_result.dart';
|
||||||
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
|
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
|
||||||
import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
|
import 'package:photos/services/machine_learning/file_ml/file_ml.dart';
|
||||||
import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart';
|
import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart';
|
||||||
import "package:photos/services/machine_learning/machine_learning_controller.dart";
|
|
||||||
import "package:photos/services/search_service.dart";
|
import "package:photos/services/search_service.dart";
|
||||||
import "package:photos/utils/file_util.dart";
|
import "package:photos/utils/file_util.dart";
|
||||||
import 'package:photos/utils/image_ml_isolate.dart';
|
import 'package:photos/utils/image_ml_isolate.dart';
|
||||||
|
@ -100,7 +99,6 @@ class FaceMlService {
|
||||||
|
|
||||||
final int _fileDownloadLimit = 5;
|
final int _fileDownloadLimit = 5;
|
||||||
final int _embeddingFetchLimit = 200;
|
final int _embeddingFetchLimit = 200;
|
||||||
final int _kForceClusteringFaceCount = 8000;
|
|
||||||
|
|
||||||
Future<void> init({bool initializeImageMlIsolate = false}) async {
|
Future<void> init({bool initializeImageMlIsolate = false}) async {
|
||||||
if (LocalSettings.instance.isFaceIndexingEnabled == false) {
|
if (LocalSettings.instance.isFaceIndexingEnabled == false) {
|
||||||
|
@ -164,16 +162,9 @@ class FaceMlService {
|
||||||
pauseIndexingAndClustering();
|
pauseIndexingAndClustering();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (Platform.isIOS &&
|
|
||||||
MachineLearningController.instance.isDeviceHealthy) {
|
|
||||||
_logger.info("Starting face indexing and clustering on iOS from init");
|
|
||||||
unawaited(indexAndClusterAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
_listenIndexOnDiffSync();
|
_listenIndexOnDiffSync();
|
||||||
_listenOnPeopleChangedSync();
|
_listenOnPeopleChangedSync();
|
||||||
|
|
||||||
_logger.info('init done');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,17 +358,16 @@ class FaceMlService {
|
||||||
if (_cannotRunMLFunction()) return;
|
if (_cannotRunMLFunction()) return;
|
||||||
|
|
||||||
await sync(forceSync: _shouldSyncPeople);
|
await sync(forceSync: _shouldSyncPeople);
|
||||||
|
await indexAllImages();
|
||||||
final int unclusteredFacesCount =
|
final indexingCompleteRatio = await _getIndexedDoneRatio();
|
||||||
await FaceMLDataDB.instance.getUnclusteredFaceCount();
|
if (indexingCompleteRatio < 0.95) {
|
||||||
if (unclusteredFacesCount > _kForceClusteringFaceCount) {
|
|
||||||
_logger.info(
|
_logger.info(
|
||||||
"There are $unclusteredFacesCount unclustered faces, doing clustering first",
|
"Indexing is not far enough to start clustering, skipping clustering. Indexing is at $indexingCompleteRatio",
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
await clusterAllImages();
|
await clusterAllImages();
|
||||||
}
|
}
|
||||||
await indexAllImages();
|
|
||||||
await clusterAllImages();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void pauseIndexingAndClustering() {
|
void pauseIndexingAndClustering() {
|
||||||
|
@ -455,8 +445,7 @@ class FaceMlService {
|
||||||
|
|
||||||
if (LocalSettings.instance.remoteFetchEnabled) {
|
if (LocalSettings.instance.remoteFetchEnabled) {
|
||||||
try {
|
try {
|
||||||
final Set<int> fileIds =
|
final List<int> fileIds = [];
|
||||||
{}; // if there are duplicates here server returns 400
|
|
||||||
// Try to find embeddings on the remote server
|
// Try to find embeddings on the remote server
|
||||||
for (final f in chunk) {
|
for (final f in chunk) {
|
||||||
fileIds.add(f.uploadedFileID!);
|
fileIds.add(f.uploadedFileID!);
|
||||||
|
@ -601,8 +590,8 @@ class FaceMlService {
|
||||||
allFaceInfoForClustering.add(faceInfo);
|
allFaceInfoForClustering.add(faceInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort the embeddings based on file creation time, newest first
|
// sort the embeddings based on file creation time, oldest first
|
||||||
allFaceInfoForClustering.sort((b, a) {
|
allFaceInfoForClustering.sort((a, b) {
|
||||||
return fileIDToCreationTime[a.fileID]!
|
return fileIDToCreationTime[a.fileID]!
|
||||||
.compareTo(fileIDToCreationTime[b.fileID]!);
|
.compareTo(fileIDToCreationTime[b.fileID]!);
|
||||||
});
|
});
|
||||||
|
@ -854,22 +843,13 @@ class FaceMlService {
|
||||||
}
|
}
|
||||||
await FaceMLDataDB.instance.bulkInsertFaces(faces);
|
await FaceMLDataDB.instance.bulkInsertFaces(faces);
|
||||||
return true;
|
return true;
|
||||||
} on ThumbnailRetrievalException catch (e, s) {
|
|
||||||
_logger.severe(
|
|
||||||
'ThumbnailRetrievalException while processing image with ID ${enteFile.uploadedFileID}, storing empty face so indexing does not get stuck',
|
|
||||||
e,
|
|
||||||
s,
|
|
||||||
);
|
|
||||||
await FaceMLDataDB.instance
|
|
||||||
.bulkInsertFaces([Face.empty(enteFile.uploadedFileID!, error: true)]);
|
|
||||||
return true;
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_logger.severe(
|
_logger.severe(
|
||||||
"Failed to analyze using FaceML for image with ID: ${enteFile.uploadedFileID}",
|
"Failed to analyze using FaceML for image with ID: ${enteFile.uploadedFileID}",
|
||||||
e,
|
e,
|
||||||
s,
|
s,
|
||||||
);
|
);
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1023,16 +1003,7 @@ class FaceMlService {
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
File? file;
|
File? file;
|
||||||
if (enteFile.fileType == FileType.video) {
|
if (enteFile.fileType == FileType.video) {
|
||||||
try {
|
file = await getThumbnailForUploadedFile(enteFile);
|
||||||
file = await getThumbnailForUploadedFile(enteFile);
|
|
||||||
} on PlatformException catch (e, s) {
|
|
||||||
_logger.severe(
|
|
||||||
"Could not get thumbnail for $enteFile due to PlatformException",
|
|
||||||
e,
|
|
||||||
s,
|
|
||||||
);
|
|
||||||
throw ThumbnailRetrievalException(e.toString(), s);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
file = await getFile(enteFile, isOrigin: true);
|
file = await getFile(enteFile, isOrigin: true);
|
||||||
// TODO: This is returning null for Pragadees for all files, so something is wrong here!
|
// TODO: This is returning null for Pragadees for all files, so something is wrong here!
|
||||||
|
@ -1200,6 +1171,24 @@ class FaceMlService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<double> _getIndexedDoneRatio() async {
|
||||||
|
final w = (kDebugMode ? EnteWatch('_getIndexedDoneRatio') : null)?..start();
|
||||||
|
|
||||||
|
final int alreadyIndexedCount = await FaceMLDataDB.instance
|
||||||
|
.getIndexedFileCount(minimumMlVersion: faceMlVersion);
|
||||||
|
final int totalIndexableCount = (await getIndexableFileIDs()).length;
|
||||||
|
final ratio = alreadyIndexedCount / totalIndexableCount;
|
||||||
|
|
||||||
|
w?.log('getIndexedDoneRatio');
|
||||||
|
|
||||||
|
return ratio;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<int>> getIndexableFileIDs() async {
|
||||||
|
return FilesDB.instance
|
||||||
|
.getOwnedFileIDs(Configuration.instance.getUserID()!);
|
||||||
|
}
|
||||||
|
|
||||||
bool _skipAnalysisEnteFile(EnteFile enteFile, Map<int, int> indexedFileIds) {
|
bool _skipAnalysisEnteFile(EnteFile enteFile, Map<int, int> indexedFileIds) {
|
||||||
if (_isIndexingOrClusteringRunning == false ||
|
if (_isIndexingOrClusteringRunning == false ||
|
||||||
_mlControllerStatus == false) {
|
_mlControllerStatus == false) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
import "dart:convert";
|
import "dart:convert";
|
||||||
|
|
||||||
import "package:computer/computer.dart";
|
|
||||||
import "package:logging/logging.dart";
|
import "package:logging/logging.dart";
|
||||||
import "package:photos/core/network/network.dart";
|
import "package:photos/core/network/network.dart";
|
||||||
import "package:photos/db/files_db.dart";
|
import "package:photos/db/files_db.dart";
|
||||||
|
@ -17,8 +16,6 @@ import "package:shared_preferences/shared_preferences.dart";
|
||||||
class RemoteFileMLService {
|
class RemoteFileMLService {
|
||||||
RemoteFileMLService._privateConstructor();
|
RemoteFileMLService._privateConstructor();
|
||||||
|
|
||||||
static final Computer _computer = Computer.shared();
|
|
||||||
|
|
||||||
static final RemoteFileMLService instance =
|
static final RemoteFileMLService instance =
|
||||||
RemoteFileMLService._privateConstructor();
|
RemoteFileMLService._privateConstructor();
|
||||||
|
|
||||||
|
@ -55,13 +52,13 @@ class RemoteFileMLService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FilesMLDataResponse> getFilessEmbedding(
|
Future<FilesMLDataResponse> getFilessEmbedding(
|
||||||
Set<int> fileIds,
|
List<int> fileIds,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final res = await _dio.post(
|
final res = await _dio.post(
|
||||||
"/embeddings/files",
|
"/embeddings/files",
|
||||||
data: {
|
data: {
|
||||||
"fileIDs": fileIds.toList(),
|
"fileIDs": fileIds,
|
||||||
"model": 'file-ml-clip-face',
|
"model": 'file-ml-clip-face',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -110,17 +107,15 @@ class RemoteFileMLService {
|
||||||
final input = EmbeddingsDecoderInput(embedding, fileKey);
|
final input = EmbeddingsDecoderInput(embedding, fileKey);
|
||||||
inputs.add(input);
|
inputs.add(input);
|
||||||
}
|
}
|
||||||
return _computer.compute<Map<String, dynamic>, Map<int, FileMl>>(
|
// todo: use compute or isolate
|
||||||
_decryptFileMLComputer,
|
return decryptFileMLComputer(
|
||||||
param: {
|
{
|
||||||
"inputs": inputs,
|
"inputs": inputs,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Future<Map<int, FileMl>> decryptFileMLComputer(
|
||||||
|
|
||||||
Future<Map<int, FileMl>> _decryptFileMLComputer(
|
|
||||||
Map<String, dynamic> args,
|
Map<String, dynamic> args,
|
||||||
) async {
|
) async {
|
||||||
final result = <int, FileMl>{};
|
final result = <int, FileMl>{};
|
||||||
|
@ -139,4 +134,5 @@ Future<Map<int, FileMl>> _decryptFileMLComputer(
|
||||||
result[input.embedding.fileID] = decodedEmbedding;
|
result[input.embedding.fileID] = decodedEmbedding;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import "dart:io";
|
||||||
import "package:battery_info/battery_info_plugin.dart";
|
import "package:battery_info/battery_info_plugin.dart";
|
||||||
import "package:battery_info/model/android_battery_info.dart";
|
import "package:battery_info/model/android_battery_info.dart";
|
||||||
import "package:battery_info/model/iso_battery_info.dart";
|
import "package:battery_info/model/iso_battery_info.dart";
|
||||||
|
import "package:flutter/foundation.dart" show kDebugMode;
|
||||||
import "package:logging/logging.dart";
|
import "package:logging/logging.dart";
|
||||||
import "package:photos/core/event_bus.dart";
|
import "package:photos/core/event_bus.dart";
|
||||||
import "package:photos/events/machine_learning_control_event.dart";
|
import "package:photos/events/machine_learning_control_event.dart";
|
||||||
|
@ -18,7 +19,8 @@ class MachineLearningController {
|
||||||
|
|
||||||
static const kMaximumTemperature = 42; // 42 degree celsius
|
static const kMaximumTemperature = 42; // 42 degree celsius
|
||||||
static const kMinimumBatteryLevel = 20; // 20%
|
static const kMinimumBatteryLevel = 20; // 20%
|
||||||
static const kDefaultInteractionTimeout = Duration(seconds: 10);
|
static const kDefaultInteractionTimeout =
|
||||||
|
kDebugMode ? Duration(seconds: 3) : Duration(seconds: 5);
|
||||||
static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"];
|
static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"];
|
||||||
|
|
||||||
bool _isDeviceHealthy = true;
|
bool _isDeviceHealthy = true;
|
||||||
|
@ -26,10 +28,7 @@ class MachineLearningController {
|
||||||
bool _canRunML = false;
|
bool _canRunML = false;
|
||||||
late Timer _userInteractionTimer;
|
late Timer _userInteractionTimer;
|
||||||
|
|
||||||
bool get isDeviceHealthy => _isDeviceHealthy;
|
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_logger.info('init called');
|
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
_startInteractionTimer();
|
_startInteractionTimer();
|
||||||
BatteryInfoPlugin()
|
BatteryInfoPlugin()
|
||||||
|
@ -46,7 +45,6 @@ class MachineLearningController {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
_fireControlEvent();
|
_fireControlEvent();
|
||||||
_logger.info('init done');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUserInteraction() {
|
void onUserInteraction() {
|
||||||
|
|
|
@ -23,7 +23,6 @@ import 'package:photos/services/machine_learning/semantic_search/frameworks/onnx
|
||||||
import "package:photos/utils/debouncer.dart";
|
import "package:photos/utils/debouncer.dart";
|
||||||
import "package:photos/utils/device_info.dart";
|
import "package:photos/utils/device_info.dart";
|
||||||
import "package:photos/utils/local_settings.dart";
|
import "package:photos/utils/local_settings.dart";
|
||||||
import "package:photos/utils/ml_util.dart";
|
|
||||||
import "package:photos/utils/thumbnail_util.dart";
|
import "package:photos/utils/thumbnail_util.dart";
|
||||||
|
|
||||||
class SemanticSearchService {
|
class SemanticSearchService {
|
||||||
|
@ -161,7 +160,8 @@ class SemanticSearchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<IndexStatus> getIndexStatus() async {
|
Future<IndexStatus> getIndexStatus() async {
|
||||||
final indexableFileIDs = await getIndexableFileIDs();
|
final indexableFileIDs = await FilesDB.instance
|
||||||
|
.getOwnedFileIDs(Configuration.instance.getUserID()!);
|
||||||
return IndexStatus(
|
return IndexStatus(
|
||||||
min(_cachedEmbeddings.length, indexableFileIDs.length),
|
min(_cachedEmbeddings.length, indexableFileIDs.length),
|
||||||
(await _getFileIDsToBeIndexed()).length,
|
(await _getFileIDsToBeIndexed()).length,
|
||||||
|
@ -222,7 +222,8 @@ class SemanticSearchService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<int>> _getFileIDsToBeIndexed() async {
|
Future<List<int>> _getFileIDsToBeIndexed() async {
|
||||||
final uploadedFileIDs = await getIndexableFileIDs();
|
final uploadedFileIDs = await FilesDB.instance
|
||||||
|
.getOwnedFileIDs(Configuration.instance.getUserID()!);
|
||||||
final embeddedFileIDs =
|
final embeddedFileIDs =
|
||||||
await EmbeddingsDB.instance.getFileIDs(_currentModel);
|
await EmbeddingsDB.instance.getFileIDs(_currentModel);
|
||||||
|
|
||||||
|
|
|
@ -1,103 +0,0 @@
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:photos/core/configuration.dart';
|
|
||||||
import 'package:photos/core/constants.dart';
|
|
||||||
import 'package:photos/core/event_bus.dart';
|
|
||||||
import 'package:photos/core/network/network.dart';
|
|
||||||
import 'package:photos/events/signed_in_event.dart';
|
|
||||||
import 'package:photos/services/sync_service.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
class PushService {
|
|
||||||
static const kFCMPushToken = "fcm_push_token";
|
|
||||||
static const kLastFCMTokenUpdationTime = "fcm_push_token_updation_time";
|
|
||||||
static const kFCMTokenUpdationIntervalInMicroSeconds = 30 * microSecondsInDay;
|
|
||||||
static const kPushAction = "action";
|
|
||||||
static const kSync = "sync";
|
|
||||||
|
|
||||||
static final PushService instance = PushService._privateConstructor();
|
|
||||||
static final _logger = Logger("PushService");
|
|
||||||
|
|
||||||
late SharedPreferences _prefs;
|
|
||||||
|
|
||||||
PushService._privateConstructor();
|
|
||||||
|
|
||||||
Future<void> init() async {
|
|
||||||
_prefs = await SharedPreferences.getInstance();
|
|
||||||
await Firebase.initializeApp();
|
|
||||||
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
|
|
||||||
_logger.info("Got a message whilst in the foreground!");
|
|
||||||
_handleForegroundPushMessage(message);
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (Configuration.instance.hasConfiguredAccount()) {
|
|
||||||
await _configurePushToken();
|
|
||||||
} else {
|
|
||||||
Bus.instance.on<SignedInEvent>().listen((_) async {
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
_configurePushToken();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
_logger.severe("Could not configure push token", e, s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _configurePushToken() async {
|
|
||||||
final String? fcmToken = await FirebaseMessaging.instance.getToken();
|
|
||||||
final shouldForceRefreshServerToken =
|
|
||||||
DateTime.now().microsecondsSinceEpoch -
|
|
||||||
(_prefs.getInt(kLastFCMTokenUpdationTime) ?? 0) >
|
|
||||||
kFCMTokenUpdationIntervalInMicroSeconds;
|
|
||||||
if (fcmToken != null &&
|
|
||||||
(_prefs.getString(kFCMPushToken) != fcmToken ||
|
|
||||||
shouldForceRefreshServerToken)) {
|
|
||||||
final String? apnsToken = await FirebaseMessaging.instance.getAPNSToken();
|
|
||||||
try {
|
|
||||||
_logger.info("Updating token on server");
|
|
||||||
await _setPushTokenOnServer(fcmToken, apnsToken);
|
|
||||||
await _prefs.setString(kFCMPushToken, fcmToken);
|
|
||||||
await _prefs.setInt(
|
|
||||||
kLastFCMTokenUpdationTime,
|
|
||||||
DateTime.now().microsecondsSinceEpoch,
|
|
||||||
);
|
|
||||||
_logger.info("Push token updated on server");
|
|
||||||
} catch (e) {
|
|
||||||
_logger.severe("Could not set push token", e, StackTrace.current);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_logger.info("Skipping token update");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setPushTokenOnServer(
|
|
||||||
String fcmToken,
|
|
||||||
String? apnsToken,
|
|
||||||
) async {
|
|
||||||
await NetworkClient.instance.enteDio.post(
|
|
||||||
"/push/token",
|
|
||||||
data: {
|
|
||||||
"fcmToken": fcmToken,
|
|
||||||
"apnsToken": apnsToken,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleForegroundPushMessage(RemoteMessage message) {
|
|
||||||
_logger.info("Message data: ${message.data}");
|
|
||||||
if (message.notification != null) {
|
|
||||||
_logger.info(
|
|
||||||
"Message also contained a notification: ${message.notification}",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (shouldSync(message)) {
|
|
||||||
SyncService.instance.sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool shouldSync(RemoteMessage message) {
|
|
||||||
return message.data.containsKey(kPushAction) &&
|
|
||||||
message.data[kPushAction] == kSync;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -754,6 +754,15 @@ class SearchService {
|
||||||
|
|
||||||
Future<List<GenericSearchResult>> getAllFace(int? limit) async {
|
Future<List<GenericSearchResult>> getAllFace(int? limit) async {
|
||||||
try {
|
try {
|
||||||
|
// Don't return anything if clustering is not nearly complete yet
|
||||||
|
final foundFaces = await FaceMLDataDB.instance.getTotalFaceCount();
|
||||||
|
final clusteredFaces =
|
||||||
|
await FaceMLDataDB.instance.getClusteredFaceCount();
|
||||||
|
final clusteringDoneRatio = clusteredFaces / foundFaces;
|
||||||
|
if (clusteringDoneRatio < 0.9) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
debugPrint("getting faces");
|
debugPrint("getting faces");
|
||||||
final Map<int, Set<int>> fileIdToClusterID =
|
final Map<int, Set<int>> fileIdToClusterID =
|
||||||
await FaceMLDataDB.instance.getFileIdToClusterIds();
|
await FaceMLDataDB.instance.getFileIdToClusterIds();
|
||||||
|
|
|
@ -1,610 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import "package:flutter/cupertino.dart";
|
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:in_app_purchase/in_app_purchase.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:photos/core/errors.dart';
|
|
||||||
import 'package:photos/core/event_bus.dart';
|
|
||||||
import 'package:photos/events/subscription_purchased_event.dart';
|
|
||||||
import "package:photos/generated/l10n.dart";
|
|
||||||
import 'package:photos/models/billing_plan.dart';
|
|
||||||
import 'package:photos/models/subscription.dart';
|
|
||||||
import 'package:photos/models/user_details.dart';
|
|
||||||
import 'package:photos/services/billing_service.dart';
|
|
||||||
import "package:photos/services/update_service.dart";
|
|
||||||
import 'package:photos/services/user_service.dart';
|
|
||||||
import "package:photos/theme/colors.dart";
|
|
||||||
import "package:photos/theme/ente_theme.dart";
|
|
||||||
import 'package:photos/ui/common/loading_widget.dart';
|
|
||||||
import 'package:photos/ui/common/progress_dialog.dart';
|
|
||||||
import "package:photos/ui/components/captioned_text_widget.dart";
|
|
||||||
import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
|
|
||||||
import 'package:photos/ui/payment/child_subscription_widget.dart';
|
|
||||||
import 'package:photos/ui/payment/skip_subscription_widget.dart';
|
|
||||||
import 'package:photos/ui/payment/subscription_common_widgets.dart';
|
|
||||||
import 'package:photos/ui/payment/subscription_plan_widget.dart';
|
|
||||||
import "package:photos/ui/payment/view_add_on_widget.dart";
|
|
||||||
import "package:photos/utils/data_util.dart";
|
|
||||||
import 'package:photos/utils/dialog_util.dart';
|
|
||||||
import 'package:photos/utils/toast_util.dart';
|
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
class StoreSubscriptionPage extends StatefulWidget {
|
|
||||||
final bool isOnboarding;
|
|
||||||
|
|
||||||
const StoreSubscriptionPage({
|
|
||||||
this.isOnboarding = false,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StoreSubscriptionPage> createState() => _StoreSubscriptionPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
|
|
||||||
final _logger = Logger("SubscriptionPage");
|
|
||||||
final _billingService = BillingService.instance;
|
|
||||||
final _userService = UserService.instance;
|
|
||||||
Subscription? _currentSubscription;
|
|
||||||
late StreamSubscription _purchaseUpdateSubscription;
|
|
||||||
late ProgressDialog _dialog;
|
|
||||||
late UserDetails _userDetails;
|
|
||||||
late bool _hasActiveSubscription;
|
|
||||||
bool _hideCurrentPlanSelection = false;
|
|
||||||
late FreePlan _freePlan;
|
|
||||||
late List<BillingPlan> _plans;
|
|
||||||
bool _hasLoadedData = false;
|
|
||||||
bool _isLoading = false;
|
|
||||||
late bool _isActiveStripeSubscriber;
|
|
||||||
EnteColorScheme colorScheme = darkScheme;
|
|
||||||
|
|
||||||
// hasYearlyPlans is used to check if there are yearly plans for given store
|
|
||||||
bool hasYearlyPlans = false;
|
|
||||||
|
|
||||||
// _showYearlyPlan is used to determine if we should show the yearly plans
|
|
||||||
bool showYearlyPlan = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_billingService.setIsOnSubscriptionPage(true);
|
|
||||||
_setupPurchaseUpdateStreamListener();
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setupPurchaseUpdateStreamListener() {
|
|
||||||
_purchaseUpdateSubscription =
|
|
||||||
InAppPurchase.instance.purchaseStream.listen((purchases) async {
|
|
||||||
if (!_dialog.isShowing()) {
|
|
||||||
await _dialog.show();
|
|
||||||
}
|
|
||||||
for (final purchase in purchases) {
|
|
||||||
_logger.info("Purchase status " + purchase.status.toString());
|
|
||||||
if (purchase.status == PurchaseStatus.purchased) {
|
|
||||||
try {
|
|
||||||
final newSubscription = await _billingService.verifySubscription(
|
|
||||||
purchase.productID,
|
|
||||||
purchase.verificationData.serverVerificationData,
|
|
||||||
);
|
|
||||||
await InAppPurchase.instance.completePurchase(purchase);
|
|
||||||
String text = S.of(context).thankYouForSubscribing;
|
|
||||||
if (!widget.isOnboarding) {
|
|
||||||
final isUpgrade = _hasActiveSubscription &&
|
|
||||||
newSubscription.storage > _currentSubscription!.storage;
|
|
||||||
final isDowngrade = _hasActiveSubscription &&
|
|
||||||
newSubscription.storage < _currentSubscription!.storage;
|
|
||||||
if (isUpgrade) {
|
|
||||||
text = S.of(context).yourPlanWasSuccessfullyUpgraded;
|
|
||||||
} else if (isDowngrade) {
|
|
||||||
text = S.of(context).yourPlanWasSuccessfullyDowngraded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showShortToast(context, text);
|
|
||||||
_currentSubscription = newSubscription;
|
|
||||||
_hasActiveSubscription = _currentSubscription!.isValid();
|
|
||||||
setState(() {});
|
|
||||||
await _dialog.hide();
|
|
||||||
Bus.instance.fire(SubscriptionPurchasedEvent());
|
|
||||||
if (widget.isOnboarding) {
|
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
|
||||||
}
|
|
||||||
} on SubscriptionAlreadyClaimedError catch (e) {
|
|
||||||
_logger.warning("subscription is already claimed ", e);
|
|
||||||
await _dialog.hide();
|
|
||||||
final String title = Platform.isAndroid
|
|
||||||
? S.of(context).playstoreSubscription
|
|
||||||
: S.of(context).appstoreSubscription;
|
|
||||||
final String id = Platform.isAndroid
|
|
||||||
? S.of(context).googlePlayId
|
|
||||||
: S.of(context).appleId;
|
|
||||||
final String message = S.of(context).subAlreadyLinkedErrMessage(id);
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
showErrorDialog(context, title, message);
|
|
||||||
return;
|
|
||||||
} catch (e) {
|
|
||||||
_logger.warning("Could not complete payment ", e);
|
|
||||||
await _dialog.hide();
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).paymentFailed,
|
|
||||||
S.of(context).paymentFailedTalkToProvider(
|
|
||||||
Platform.isAndroid ? "PlayStore" : "AppStore",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (Platform.isIOS && purchase.pendingCompletePurchase) {
|
|
||||||
await InAppPurchase.instance.completePurchase(purchase);
|
|
||||||
await _dialog.hide();
|
|
||||||
} else if (purchase.status == PurchaseStatus.error) {
|
|
||||||
await _dialog.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_purchaseUpdateSubscription.cancel();
|
|
||||||
_billingService.setIsOnSubscriptionPage(false);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
colorScheme = getEnteColorScheme(context);
|
|
||||||
if (!_isLoading) {
|
|
||||||
_isLoading = true;
|
|
||||||
_fetchSubData();
|
|
||||||
}
|
|
||||||
_dialog = createProgressDialog(context, S.of(context).pleaseWait);
|
|
||||||
final appBar = AppBar(
|
|
||||||
title: widget.isOnboarding
|
|
||||||
? null
|
|
||||||
: Text("${S.of(context).subscription}${kDebugMode ? ' Store' : ''}"),
|
|
||||||
);
|
|
||||||
return Scaffold(
|
|
||||||
appBar: appBar,
|
|
||||||
body: _getBody(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isFreePlanUser() {
|
|
||||||
return _currentSubscription != null &&
|
|
||||||
freeProductID == _currentSubscription!.productID;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _fetchSubData() async {
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
_userService.getUserDetailsV2(memoryCount: false).then((userDetails) async {
|
|
||||||
_userDetails = userDetails;
|
|
||||||
_currentSubscription = userDetails.subscription;
|
|
||||||
|
|
||||||
_hasActiveSubscription = _currentSubscription!.isValid();
|
|
||||||
_hideCurrentPlanSelection =
|
|
||||||
_currentSubscription?.attributes?.isCancelled ?? false;
|
|
||||||
showYearlyPlan = _currentSubscription!.isYearlyPlan();
|
|
||||||
final billingPlans = await _billingService.getBillingPlans();
|
|
||||||
_isActiveStripeSubscriber =
|
|
||||||
_currentSubscription!.paymentProvider == stripe &&
|
|
||||||
_currentSubscription!.isValid();
|
|
||||||
_plans = billingPlans.plans.where((plan) {
|
|
||||||
final productID = _isActiveStripeSubscriber
|
|
||||||
? plan.stripeID
|
|
||||||
: Platform.isAndroid
|
|
||||||
? plan.androidID
|
|
||||||
: plan.iosID;
|
|
||||||
return productID.isNotEmpty;
|
|
||||||
}).toList();
|
|
||||||
hasYearlyPlans = _plans.any((plan) => plan.period == 'year');
|
|
||||||
if (showYearlyPlan && hasYearlyPlans) {
|
|
||||||
_plans = _plans.where((plan) => plan.period == 'year').toList();
|
|
||||||
} else {
|
|
||||||
_plans = _plans.where((plan) => plan.period != 'year').toList();
|
|
||||||
}
|
|
||||||
_freePlan = billingPlans.freePlan;
|
|
||||||
_hasLoadedData = true;
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _getBody() {
|
|
||||||
if (_hasLoadedData) {
|
|
||||||
if (_userDetails.isPartOfFamily() && !_userDetails.isFamilyAdmin()) {
|
|
||||||
return ChildSubscriptionWidget(userDetails: _userDetails);
|
|
||||||
} else {
|
|
||||||
return _buildPlans();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return const EnteLoadingWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildPlans() {
|
|
||||||
final widgets = <Widget>[];
|
|
||||||
widgets.add(
|
|
||||||
SubscriptionHeaderWidget(
|
|
||||||
isOnboarding: widget.isOnboarding,
|
|
||||||
currentUsage: _userDetails.getFamilyOrPersonalUsage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
widgets.addAll([
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: _isActiveStripeSubscriber
|
|
||||||
? _getStripePlanWidgets()
|
|
||||||
: _getMobilePlanWidgets(),
|
|
||||||
),
|
|
||||||
const Padding(padding: EdgeInsets.all(8)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (hasYearlyPlans) {
|
|
||||||
widgets.add(_showSubscriptionToggle());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentSubscription != null) {
|
|
||||||
widgets.add(
|
|
||||||
ValidityWidget(
|
|
||||||
currentSubscription: _currentSubscription,
|
|
||||||
bonusData: _userDetails.bonusData,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentSubscription!.productID == freeProductID) {
|
|
||||||
if (widget.isOnboarding) {
|
|
||||||
widgets.add(SkipSubscriptionWidget(freePlan: _freePlan));
|
|
||||||
}
|
|
||||||
widgets.add(
|
|
||||||
SubFaqWidget(isOnboarding: widget.isOnboarding),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_hasActiveSubscription &&
|
|
||||||
_currentSubscription!.productID != freeProductID) {
|
|
||||||
if (_isActiveStripeSubscriber) {
|
|
||||||
widgets.add(
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 20),
|
|
||||||
child: Text(
|
|
||||||
S.of(context).visitWebToManage,
|
|
||||||
style: getEnteTextTheme(context).small.copyWith(
|
|
||||||
color: colorScheme.textMuted,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
widgets.add(
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 40, 16, 4),
|
|
||||||
child: MenuItemWidget(
|
|
||||||
captionedTextWidget: CaptionedTextWidget(
|
|
||||||
title: S.of(context).paymentDetails,
|
|
||||||
),
|
|
||||||
menuItemColor: colorScheme.fillFaint,
|
|
||||||
trailingWidget: Icon(
|
|
||||||
Icons.chevron_right_outlined,
|
|
||||||
color: colorScheme.strokeBase,
|
|
||||||
),
|
|
||||||
singleBorderRadius: 4,
|
|
||||||
alignCaptionedTextToLeft: true,
|
|
||||||
onTap: () async {
|
|
||||||
_onPlatformRestrictedPaymentDetailsClick();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!widget.isOnboarding) {
|
|
||||||
widgets.add(
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
|
|
||||||
child: MenuItemWidget(
|
|
||||||
captionedTextWidget: CaptionedTextWidget(
|
|
||||||
title: _isFreePlanUser()
|
|
||||||
? S.of(context).familyPlans
|
|
||||||
: S.of(context).manageFamily,
|
|
||||||
),
|
|
||||||
menuItemColor: colorScheme.fillFaint,
|
|
||||||
trailingWidget: Icon(
|
|
||||||
Icons.chevron_right_outlined,
|
|
||||||
color: colorScheme.strokeBase,
|
|
||||||
),
|
|
||||||
singleBorderRadius: 4,
|
|
||||||
alignCaptionedTextToLeft: true,
|
|
||||||
onTap: () async {
|
|
||||||
unawaited(
|
|
||||||
_billingService.launchFamilyPortal(context, _userDetails),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
widgets.add(ViewAddOnButton(_userDetails.bonusData));
|
|
||||||
widgets.add(const SizedBox(height: 80));
|
|
||||||
}
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: widgets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onPlatformRestrictedPaymentDetailsClick() {
|
|
||||||
final String paymentProvider = _currentSubscription!.paymentProvider;
|
|
||||||
if (paymentProvider == appStore && !Platform.isAndroid) {
|
|
||||||
launchUrlString("https://apps.apple.com/account/billing");
|
|
||||||
} else if (paymentProvider == playStore && Platform.isAndroid) {
|
|
||||||
launchUrlString(
|
|
||||||
"https://play.google.com/store/account/subscriptions?sku=" +
|
|
||||||
_currentSubscription!.productID +
|
|
||||||
"&package=io.ente.photos",
|
|
||||||
);
|
|
||||||
} else if (paymentProvider == stripe) {
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).sorry,
|
|
||||||
S.of(context).visitWebToManage,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
final String capitalizedWord = paymentProvider.isNotEmpty
|
|
||||||
? '${paymentProvider[0].toUpperCase()}${paymentProvider.substring(1).toLowerCase()}'
|
|
||||||
: '';
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).sorry,
|
|
||||||
S.of(context).contactToManageSubscription(capitalizedWord),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _filterStorePlansForUi() async {
|
|
||||||
final billingPlans = await _billingService.getBillingPlans();
|
|
||||||
_plans = billingPlans.plans.where((plan) {
|
|
||||||
final productID = _isActiveStripeSubscriber
|
|
||||||
? plan.stripeID
|
|
||||||
: Platform.isAndroid
|
|
||||||
? plan.androidID
|
|
||||||
: plan.iosID;
|
|
||||||
return productID.isNotEmpty;
|
|
||||||
}).toList();
|
|
||||||
hasYearlyPlans = _plans.any((plan) => plan.period == 'year');
|
|
||||||
if (showYearlyPlan) {
|
|
||||||
_plans = _plans.where((plan) => plan.period == 'year').toList();
|
|
||||||
} else {
|
|
||||||
_plans = _plans.where((plan) => plan.period != 'year').toList();
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _showSubscriptionToggle() {
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2),
|
|
||||||
margin: const EdgeInsets.only(bottom: 6),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
RepaintBoundary(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 250,
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: SegmentedButton(
|
|
||||||
style: SegmentedButton.styleFrom(
|
|
||||||
selectedBackgroundColor:
|
|
||||||
getEnteColorScheme(context).fillMuted,
|
|
||||||
selectedForegroundColor:
|
|
||||||
getEnteColorScheme(context).textBase,
|
|
||||||
side: BorderSide(
|
|
||||||
color: getEnteColorScheme(context).strokeMuted,
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
segments: <ButtonSegment<bool>>[
|
|
||||||
ButtonSegment(
|
|
||||||
label: Text(S.of(context).monthly),
|
|
||||||
value: false,
|
|
||||||
),
|
|
||||||
ButtonSegment(
|
|
||||||
label: Text(S.of(context).yearly),
|
|
||||||
value: true,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
selected: {showYearlyPlan},
|
|
||||||
onSelectionChanged: (p0) {
|
|
||||||
showYearlyPlan = p0.first;
|
|
||||||
_filterStorePlansForUi();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
_isFreePlanUser() && !UpdateService.instance.isPlayStoreFlavor()
|
|
||||||
? Text(
|
|
||||||
S.of(context).twoMonthsFreeOnYearlyPlans,
|
|
||||||
style: getEnteTextTheme(context).miniMuted,
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
const Padding(padding: EdgeInsets.all(8)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _getStripePlanWidgets() {
|
|
||||||
final List<Widget> planWidgets = [];
|
|
||||||
bool foundActivePlan = false;
|
|
||||||
for (final plan in _plans) {
|
|
||||||
final productID = plan.stripeID;
|
|
||||||
if (productID.isEmpty) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final isActive = _hasActiveSubscription &&
|
|
||||||
_currentSubscription!.productID == productID;
|
|
||||||
if (isActive) {
|
|
||||||
foundActivePlan = true;
|
|
||||||
}
|
|
||||||
planWidgets.add(
|
|
||||||
Material(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
if (isActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).sorry,
|
|
||||||
S.of(context).visitWebToManage,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: SubscriptionPlanWidget(
|
|
||||||
storage: plan.storage,
|
|
||||||
price: plan.price,
|
|
||||||
period: plan.period,
|
|
||||||
isActive: isActive && !_hideCurrentPlanSelection,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!foundActivePlan && _hasActiveSubscription) {
|
|
||||||
_addCurrentPlanWidget(planWidgets);
|
|
||||||
}
|
|
||||||
return planWidgets;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> _getMobilePlanWidgets() {
|
|
||||||
bool foundActivePlan = false;
|
|
||||||
final List<Widget> planWidgets = [];
|
|
||||||
if (_hasActiveSubscription &&
|
|
||||||
_currentSubscription!.productID == freeProductID) {
|
|
||||||
foundActivePlan = true;
|
|
||||||
planWidgets.add(
|
|
||||||
SubscriptionPlanWidget(
|
|
||||||
storage: _freePlan.storage,
|
|
||||||
price: S.of(context).freeTrial,
|
|
||||||
period: "",
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final plan in _plans) {
|
|
||||||
final productID = Platform.isAndroid ? plan.androidID : plan.iosID;
|
|
||||||
final isActive = _hasActiveSubscription &&
|
|
||||||
_currentSubscription!.productID == productID;
|
|
||||||
if (isActive) {
|
|
||||||
foundActivePlan = true;
|
|
||||||
}
|
|
||||||
planWidgets.add(
|
|
||||||
Material(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () async {
|
|
||||||
if (isActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final int addOnBonus =
|
|
||||||
_userDetails.bonusData?.totalAddOnBonus() ?? 0;
|
|
||||||
if (_userDetails.getFamilyOrPersonalUsage() >
|
|
||||||
(plan.storage + addOnBonus)) {
|
|
||||||
_logger.warning(
|
|
||||||
" familyUsage ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())}"
|
|
||||||
" plan storage ${convertBytesToReadableFormat(plan.storage)} "
|
|
||||||
"addOnBonus ${convertBytesToReadableFormat(addOnBonus)},"
|
|
||||||
"overshooting by ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage() - (plan.storage + addOnBonus))}",
|
|
||||||
);
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).sorry,
|
|
||||||
S.of(context).youCannotDowngradeToThisPlan,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await _dialog.show();
|
|
||||||
final ProductDetailsResponse response =
|
|
||||||
await InAppPurchase.instance.queryProductDetails({productID});
|
|
||||||
if (response.notFoundIDs.isNotEmpty) {
|
|
||||||
final errMsg = "Could not find products: " +
|
|
||||||
response.notFoundIDs.toString();
|
|
||||||
_logger.severe(errMsg);
|
|
||||||
await _dialog.hide();
|
|
||||||
await showGenericErrorDialog(
|
|
||||||
context: context,
|
|
||||||
error: Exception(errMsg),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final isCrossGradingOnAndroid = Platform.isAndroid &&
|
|
||||||
_hasActiveSubscription &&
|
|
||||||
_currentSubscription!.productID != freeProductID &&
|
|
||||||
_currentSubscription!.productID != plan.androidID;
|
|
||||||
if (isCrossGradingOnAndroid) {
|
|
||||||
await _dialog.hide();
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
showErrorDialog(
|
|
||||||
context,
|
|
||||||
S.of(context).couldNotUpdateSubscription,
|
|
||||||
S.of(context).pleaseContactSupportAndWeWillBeHappyToHelp,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
await InAppPurchase.instance.buyNonConsumable(
|
|
||||||
purchaseParam: PurchaseParam(
|
|
||||||
productDetails: response.productDetails[0],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SubscriptionPlanWidget(
|
|
||||||
storage: plan.storage,
|
|
||||||
price: plan.price,
|
|
||||||
period: plan.period,
|
|
||||||
isActive: isActive,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!foundActivePlan && _hasActiveSubscription) {
|
|
||||||
_addCurrentPlanWidget(planWidgets);
|
|
||||||
}
|
|
||||||
return planWidgets;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addCurrentPlanWidget(List<Widget> planWidgets) {
|
|
||||||
int activePlanIndex = 0;
|
|
||||||
for (; activePlanIndex < _plans.length; activePlanIndex++) {
|
|
||||||
if (_plans[activePlanIndex].storage > _currentSubscription!.storage) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
planWidgets.insert(
|
|
||||||
activePlanIndex,
|
|
||||||
Material(
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {},
|
|
||||||
child: SubscriptionPlanWidget(
|
|
||||||
storage: _currentSubscription!.storage,
|
|
||||||
price: _currentSubscription!.price,
|
|
||||||
period: _currentSubscription!.period,
|
|
||||||
isActive: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +1,6 @@
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:photos/core/configuration.dart';
|
|
||||||
import "package:photos/service_locator.dart";
|
|
||||||
import 'package:photos/services/update_service.dart';
|
|
||||||
import "package:photos/ui/payment/store_subscription_page.dart";
|
|
||||||
import 'package:photos/ui/payment/stripe_subscription_page.dart';
|
import 'package:photos/ui/payment/stripe_subscription_page.dart';
|
||||||
|
|
||||||
StatefulWidget getSubscriptionPage({bool isOnBoarding = false}) {
|
StatefulWidget getSubscriptionPage({bool isOnBoarding = false}) {
|
||||||
if (UpdateService.instance.isIndependentFlavor()) {
|
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
|
||||||
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
|
|
||||||
}
|
|
||||||
if (flagService.enableStripe && _isUserCreatedPostStripeSupport()) {
|
|
||||||
return StripeSubscriptionPage(isOnboarding: isOnBoarding);
|
|
||||||
} else {
|
|
||||||
return StoreSubscriptionPage(isOnboarding: isOnBoarding);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return true if the user was created after we added support for stripe payment
|
|
||||||
// on frame. We do this check to avoid showing Stripe payment option for earlier
|
|
||||||
// users who might have paid via playStore. This method should be removed once
|
|
||||||
// we have better handling for active play/app store subscription & stripe plans.
|
|
||||||
bool _isUserCreatedPostStripeSupport() {
|
|
||||||
return Configuration.instance.getUserID()! > 1580559962386460;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,7 +177,7 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
|
||||||
sectionOptionSpacing,
|
sectionOptionSpacing,
|
||||||
MenuItemWidget(
|
MenuItemWidget(
|
||||||
captionedTextWidget: FutureBuilder<double>(
|
captionedTextWidget: FutureBuilder<double>(
|
||||||
future: FaceMLDataDB.instance.getClusteredToIndexableFilesRatio(),
|
future: FaceMLDataDB.instance.getClusteredToTotalFacesRatio(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
return CaptionedTextWidget(
|
return CaptionedTextWidget(
|
||||||
|
|
|
@ -11,7 +11,6 @@ import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/models/ml/ml_versions.dart";
|
import "package:photos/models/ml/ml_versions.dart";
|
||||||
import "package:photos/service_locator.dart";
|
import "package:photos/service_locator.dart";
|
||||||
import "package:photos/services/machine_learning/face_ml/face_ml_service.dart";
|
import "package:photos/services/machine_learning/face_ml/face_ml_service.dart";
|
||||||
import "package:photos/services/machine_learning/machine_learning_controller.dart";
|
|
||||||
import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart';
|
import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart';
|
||||||
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
||||||
import "package:photos/services/remote_assets_service.dart";
|
import "package:photos/services/remote_assets_service.dart";
|
||||||
|
@ -27,8 +26,6 @@ import "package:photos/ui/components/title_bar_widget.dart";
|
||||||
import "package:photos/ui/components/toggle_switch_widget.dart";
|
import "package:photos/ui/components/toggle_switch_widget.dart";
|
||||||
import "package:photos/utils/data_util.dart";
|
import "package:photos/utils/data_util.dart";
|
||||||
import "package:photos/utils/local_settings.dart";
|
import "package:photos/utils/local_settings.dart";
|
||||||
import "package:photos/utils/ml_util.dart";
|
|
||||||
import "package:photos/utils/wakelock_util.dart";
|
|
||||||
|
|
||||||
final _logger = Logger("MachineLearningSettingsPage");
|
final _logger = Logger("MachineLearningSettingsPage");
|
||||||
|
|
||||||
|
@ -43,7 +40,6 @@ class MachineLearningSettingsPage extends StatefulWidget {
|
||||||
class _MachineLearningSettingsPageState
|
class _MachineLearningSettingsPageState
|
||||||
extends State<MachineLearningSettingsPage> {
|
extends State<MachineLearningSettingsPage> {
|
||||||
late InitializationState _state;
|
late InitializationState _state;
|
||||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
|
||||||
|
|
||||||
late StreamSubscription<MLFrameworkInitializationUpdateEvent>
|
late StreamSubscription<MLFrameworkInitializationUpdateEvent>
|
||||||
_eventSubscription;
|
_eventSubscription;
|
||||||
|
@ -57,7 +53,6 @@ class _MachineLearningSettingsPageState
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
_fetchState();
|
_fetchState();
|
||||||
_wakeLock.enable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchState() {
|
void _fetchState() {
|
||||||
|
@ -68,7 +63,6 @@ class _MachineLearningSettingsPageState
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
_eventSubscription.cancel();
|
_eventSubscription.cancel();
|
||||||
_wakeLock.disable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -89,8 +83,8 @@ class _MachineLearningSettingsPageState
|
||||||
iconButtonType: IconButtonType.secondary,
|
iconButtonType: IconButtonType.secondary,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (Navigator.canPop(context)) Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
if (Navigator.canPop(context)) Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -444,24 +438,19 @@ class FaceRecognitionStatusWidgetState
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(int, int, double, bool)> getIndexStatus() async {
|
Future<(int, int, int, double)> getIndexStatus() async {
|
||||||
try {
|
try {
|
||||||
final indexedFiles = await FaceMLDataDB.instance
|
final indexedFiles = await FaceMLDataDB.instance
|
||||||
.getIndexedFileCount(minimumMlVersion: faceMlVersion);
|
.getIndexedFileCount(minimumMlVersion: faceMlVersion);
|
||||||
final indexableFiles = (await getIndexableFileIDs()).length;
|
final indexableFiles = (await FaceMlService.getIndexableFileIDs()).length;
|
||||||
final showIndexedFiles = min(indexedFiles, indexableFiles);
|
final showIndexedFiles = min(indexedFiles, indexableFiles);
|
||||||
final pendingFiles = max(indexableFiles - indexedFiles, 0);
|
final pendingFiles = max(indexableFiles - indexedFiles, 0);
|
||||||
final clusteringDoneRatio =
|
final foundFaces = await FaceMLDataDB.instance.getTotalFaceCount();
|
||||||
await FaceMLDataDB.instance.getClusteredToIndexableFilesRatio();
|
final clusteredFaces =
|
||||||
final bool deviceIsHealthy =
|
await FaceMLDataDB.instance.getClusteredFaceCount();
|
||||||
MachineLearningController.instance.isDeviceHealthy;
|
final clusteringDoneRatio = clusteredFaces / foundFaces;
|
||||||
|
|
||||||
return (
|
return (showIndexedFiles, pendingFiles, foundFaces, clusteringDoneRatio);
|
||||||
showIndexedFiles,
|
|
||||||
pendingFiles,
|
|
||||||
clusteringDoneRatio,
|
|
||||||
deviceIsHealthy
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_logger.severe('Error getting face recognition status', e, s);
|
_logger.severe('Error getting face recognition status', e, s);
|
||||||
rethrow;
|
rethrow;
|
||||||
|
@ -490,17 +479,10 @@ class FaceRecognitionStatusWidgetState
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final int indexedFiles = snapshot.data!.$1;
|
final int indexedFiles = snapshot.data!.$1;
|
||||||
final int pendingFiles = snapshot.data!.$2;
|
final int pendingFiles = snapshot.data!.$2;
|
||||||
final double clusteringDoneRatio = snapshot.data!.$3;
|
final int foundFaces = snapshot.data!.$3;
|
||||||
|
final double clusteringDoneRatio = snapshot.data!.$4;
|
||||||
final double clusteringPercentage =
|
final double clusteringPercentage =
|
||||||
(clusteringDoneRatio * 100).clamp(0, 100);
|
(clusteringDoneRatio * 100).clamp(0, 100);
|
||||||
final bool isDeviceHealthy = snapshot.data!.$4;
|
|
||||||
|
|
||||||
if (!isDeviceHealthy &&
|
|
||||||
(pendingFiles > 0 || clusteringPercentage < 99)) {
|
|
||||||
return MenuSectionDescriptionWidget(
|
|
||||||
content: S.of(context).indexingIsPaused,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
|
@ -530,6 +512,19 @@ class FaceRecognitionStatusWidgetState
|
||||||
isGestureDetectorDisabled: true,
|
isGestureDetectorDisabled: true,
|
||||||
key: ValueKey("pending_items_" + pendingFiles.toString()),
|
key: ValueKey("pending_items_" + pendingFiles.toString()),
|
||||||
),
|
),
|
||||||
|
MenuItemWidget(
|
||||||
|
captionedTextWidget: CaptionedTextWidget(
|
||||||
|
title: S.of(context).foundFaces,
|
||||||
|
),
|
||||||
|
trailingWidget: Text(
|
||||||
|
NumberFormat().format(foundFaces),
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
singleBorderRadius: 8,
|
||||||
|
alignCaptionedTextToLeft: true,
|
||||||
|
isGestureDetectorDisabled: true,
|
||||||
|
key: ValueKey("found_faces_" + foundFaces.toString()),
|
||||||
|
),
|
||||||
MenuItemWidget(
|
MenuItemWidget(
|
||||||
captionedTextWidget: CaptionedTextWidget(
|
captionedTextWidget: CaptionedTextWidget(
|
||||||
title: S.of(context).clusteringProgress,
|
title: S.of(context).clusteringProgress,
|
||||||
|
|
|
@ -17,9 +17,9 @@ import 'package:photos/ui/viewer/file/video_controls.dart';
|
||||||
import "package:photos/utils/dialog_util.dart";
|
import "package:photos/utils/dialog_util.dart";
|
||||||
import 'package:photos/utils/file_util.dart';
|
import 'package:photos/utils/file_util.dart';
|
||||||
import 'package:photos/utils/toast_util.dart';
|
import 'package:photos/utils/toast_util.dart';
|
||||||
import "package:photos/utils/wakelock_util.dart";
|
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
class VideoWidget extends StatefulWidget {
|
class VideoWidget extends StatefulWidget {
|
||||||
final EnteFile file;
|
final EnteFile file;
|
||||||
|
@ -45,7 +45,7 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||||
ChewieController? _chewieController;
|
ChewieController? _chewieController;
|
||||||
final _progressNotifier = ValueNotifier<double?>(null);
|
final _progressNotifier = ValueNotifier<double?>(null);
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
bool _wakeLockEnabledHere = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -126,7 +126,13 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||||
_chewieController?.dispose();
|
_chewieController?.dispose();
|
||||||
_progressNotifier.dispose();
|
_progressNotifier.dispose();
|
||||||
|
|
||||||
_wakeLock.dispose();
|
if (_wakeLockEnabledHere) {
|
||||||
|
unawaited(
|
||||||
|
WakelockPlus.enabled.then((isEnabled) {
|
||||||
|
isEnabled ? WakelockPlus.disable() : null;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,10 +257,17 @@ class _VideoWidgetState extends State<VideoWidget> {
|
||||||
|
|
||||||
Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
Future<void> _keepScreenAliveOnPlaying(bool isPlaying) async {
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
_wakeLock.enable();
|
return WakelockPlus.enabled.then((value) {
|
||||||
|
if (value == false) {
|
||||||
|
WakelockPlus.enable();
|
||||||
|
//wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
|
||||||
|
//We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
|
||||||
|
_wakeLockEnabledHere = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (!isPlaying) {
|
if (_wakeLockEnabledHere && !isPlaying) {
|
||||||
_wakeLock.disable();
|
return WakelockPlus.disable();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_animate/flutter_animate.dart";
|
import "package:flutter_animate/flutter_animate.dart";
|
||||||
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
|
||||||
import "package:photos/core/configuration.dart";
|
import "package:photos/core/configuration.dart";
|
||||||
import "package:photos/core/constants.dart";
|
|
||||||
import "package:photos/db/files_db.dart";
|
import "package:photos/db/files_db.dart";
|
||||||
import "package:photos/generated/l10n.dart";
|
import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/l10n/l10n.dart";
|
import "package:photos/l10n/l10n.dart";
|
||||||
|
@ -168,14 +167,7 @@ class AddPhotosPhotoWidget extends StatelessWidget {
|
||||||
|
|
||||||
Future<void> _onPickFromDeviceClicked(BuildContext context) async {
|
Future<void> _onPickFromDeviceClicked(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
final assetPickerTextDelegate = await _getAssetPickerTextDelegate();
|
final List<AssetEntity>? result = await AssetPicker.pickAssets(context);
|
||||||
final List<AssetEntity>? result = await AssetPicker.pickAssets(
|
|
||||||
context,
|
|
||||||
pickerConfig: AssetPickerConfig(
|
|
||||||
maxAssets: maxPickAssetLimit,
|
|
||||||
textDelegate: assetPickerTextDelegate,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (result != null && result.isNotEmpty) {
|
if (result != null && result.isNotEmpty) {
|
||||||
final ca = CollectionActions(
|
final ca = CollectionActions(
|
||||||
CollectionsService.instance,
|
CollectionsService.instance,
|
||||||
|
@ -212,39 +204,6 @@ class AddPhotosPhotoWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// _getAssetPickerTextDelegate returns the text delegate for the asset picker
|
|
||||||
// This custom method is required to enforce English as the default fallback
|
|
||||||
// instead of Chinese.
|
|
||||||
Future<AssetPickerTextDelegate> _getAssetPickerTextDelegate() async {
|
|
||||||
final Locale locale = await getLocale();
|
|
||||||
switch (locale.languageCode.toLowerCase()) {
|
|
||||||
case "en":
|
|
||||||
return const EnglishAssetPickerTextDelegate();
|
|
||||||
case "he":
|
|
||||||
return const HebrewAssetPickerTextDelegate();
|
|
||||||
case "de":
|
|
||||||
return const GermanAssetPickerTextDelegate();
|
|
||||||
case "ru":
|
|
||||||
return const RussianAssetPickerTextDelegate();
|
|
||||||
case "ja":
|
|
||||||
return const JapaneseAssetPickerTextDelegate();
|
|
||||||
case "ar":
|
|
||||||
return const ArabicAssetPickerTextDelegate();
|
|
||||||
case "fr":
|
|
||||||
return const FrenchAssetPickerTextDelegate();
|
|
||||||
case "vi":
|
|
||||||
return const VietnameseAssetPickerTextDelegate();
|
|
||||||
case "tr":
|
|
||||||
return const TurkishAssetPickerTextDelegate();
|
|
||||||
case "ko":
|
|
||||||
return const KoreanAssetPickerTextDelegate();
|
|
||||||
case "zh":
|
|
||||||
return const AssetPickerTextDelegate();
|
|
||||||
default:
|
|
||||||
return const EnglishAssetPickerTextDelegate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DelayedGallery extends StatefulWidget {
|
class DelayedGallery extends StatefulWidget {
|
||||||
|
|
|
@ -97,7 +97,7 @@ class _AppBarWidgetState extends State<ClusterAppBar> {
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
actions: _getDefaultActions(context),
|
actions: kDebugMode ? _getDefaultActions(context) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,45 +161,43 @@ class _ClusterPageState extends State<ClusterPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
showNamingBanner
|
showNamingBanner
|
||||||
? SafeArea(
|
? Dismissible(
|
||||||
child: Dismissible(
|
key: const Key("namingBanner"),
|
||||||
key: const Key("namingBanner"),
|
direction: DismissDirection.horizontal,
|
||||||
direction: DismissDirection.horizontal,
|
onDismissed: (direction) {
|
||||||
onDismissed: (direction) {
|
setState(() {
|
||||||
setState(() {
|
userDismissedNamingBanner = true;
|
||||||
userDismissedNamingBanner = true;
|
});
|
||||||
});
|
},
|
||||||
},
|
child: PeopleBanner(
|
||||||
child: PeopleBanner(
|
type: PeopleBannerType.addName,
|
||||||
type: PeopleBannerType.addName,
|
faceWidget: PersonFaceWidget(
|
||||||
faceWidget: PersonFaceWidget(
|
files.first,
|
||||||
files.first,
|
clusterID: widget.clusterID,
|
||||||
clusterID: widget.clusterID,
|
|
||||||
),
|
|
||||||
actionIcon: Icons.add_outlined,
|
|
||||||
text: S.of(context).addAName,
|
|
||||||
subText: S.of(context).findPeopleByName,
|
|
||||||
onTap: () async {
|
|
||||||
if (widget.personID == null) {
|
|
||||||
final result = await showAssignPersonAction(
|
|
||||||
context,
|
|
||||||
clusterID: widget.clusterID,
|
|
||||||
);
|
|
||||||
if (result != null &&
|
|
||||||
result is (PersonEntity, EnteFile)) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
routeToPage(context, PeoplePage(person: result.$1));
|
|
||||||
} else if (result != null && result is PersonEntity) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
// ignore: unawaited_futures
|
|
||||||
routeToPage(context, PeoplePage(person: result));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showShortToast(context, "No personID or clusterID");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
actionIcon: Icons.add_outlined,
|
||||||
|
text: S.of(context).addAName,
|
||||||
|
subText: S.of(context).findPeopleByName,
|
||||||
|
onTap: () async {
|
||||||
|
if (widget.personID == null) {
|
||||||
|
final result = await showAssignPersonAction(
|
||||||
|
context,
|
||||||
|
clusterID: widget.clusterID,
|
||||||
|
);
|
||||||
|
if (result != null &&
|
||||||
|
result is (PersonEntity, EnteFile)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
// ignore: unawaited_futures
|
||||||
|
routeToPage(context, PeoplePage(person: result.$1));
|
||||||
|
} else if (result != null && result is PersonEntity) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
// ignore: unawaited_futures
|
||||||
|
routeToPage(context, PeoplePage(person: result));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showShortToast(context, "No personID or clusterID");
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
|
|
|
@ -38,17 +38,12 @@ class _PersonClustersPageState extends State<PersonClustersPage> {
|
||||||
.getClusterFilesForPersonID(widget.person.remoteID),
|
.getClusterFilesForPersonID(widget.person.remoteID),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasData) {
|
if (snapshot.hasData) {
|
||||||
final clusters = snapshot.data!;
|
final List<int> keys = snapshot.data!.keys.toList();
|
||||||
final List<int> keys = clusters.keys.toList();
|
|
||||||
// Sort the clusters by the number of files in each cluster, largest first
|
|
||||||
keys.sort(
|
|
||||||
(b, a) => clusters[a]!.length.compareTo(clusters[b]!.length),
|
|
||||||
);
|
|
||||||
return ListView.builder(
|
return ListView.builder(
|
||||||
itemCount: keys.length,
|
itemCount: keys.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final int clusterID = keys[index];
|
final int clusterID = keys[index];
|
||||||
final List<EnteFile> files = clusters[clusterID]!;
|
final List<EnteFile> files = snapshot.data![keys[index]]!;
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
|
@ -98,37 +93,34 @@ class _PersonClustersPageState extends State<PersonClustersPage> {
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"${files.length} photos",
|
"${snapshot.data![keys[index]]!.length} photos",
|
||||||
style: getEnteTextTheme(context).body,
|
style: getEnteTextTheme(context).body,
|
||||||
),
|
),
|
||||||
(index != 0)
|
GestureDetector(
|
||||||
? GestureDetector(
|
onTap: () async {
|
||||||
onTap: () async {
|
try {
|
||||||
try {
|
await PersonService.instance
|
||||||
await PersonService.instance
|
.removeClusterToPerson(
|
||||||
.removeClusterToPerson(
|
personID: widget.person.remoteID,
|
||||||
personID: widget.person.remoteID,
|
clusterID: clusterID,
|
||||||
clusterID: clusterID,
|
);
|
||||||
);
|
_logger.info(
|
||||||
_logger.info(
|
"Removed cluster $clusterID from person ${widget.person.remoteID}",
|
||||||
"Removed cluster $clusterID from person ${widget.person.remoteID}",
|
);
|
||||||
);
|
Bus.instance.fire(PeopleChangedEvent());
|
||||||
Bus.instance
|
setState(() {});
|
||||||
.fire(PeopleChangedEvent());
|
} catch (e) {
|
||||||
setState(() {});
|
_logger.severe(
|
||||||
} catch (e) {
|
"removing cluster from person,",
|
||||||
_logger.severe(
|
e,
|
||||||
"removing cluster from person,",
|
);
|
||||||
e,
|
}
|
||||||
);
|
},
|
||||||
}
|
child: const Icon(
|
||||||
},
|
CupertinoIcons.minus_circled,
|
||||||
child: const Icon(
|
color: Colors.red,
|
||||||
CupertinoIcons.minus_circled,
|
),
|
||||||
color: Colors.red,
|
),
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -203,7 +203,7 @@ class SearchWidgetState extends State<SearchWidget> {
|
||||||
String query,
|
String query,
|
||||||
) {
|
) {
|
||||||
int resultCount = 0;
|
int resultCount = 0;
|
||||||
final maxResultCount = _isYearValid(query) ? 12 : 11;
|
final maxResultCount = _isYearValid(query) ? 13 : 12;
|
||||||
final streamController = StreamController<List<SearchResult>>();
|
final streamController = StreamController<List<SearchResult>>();
|
||||||
|
|
||||||
if (query.isEmpty) {
|
if (query.isEmpty) {
|
||||||
|
@ -260,11 +260,10 @@ class SearchWidgetState extends State<SearchWidget> {
|
||||||
onResultsReceived(locationResult);
|
onResultsReceived(locationResult);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
_searchService.getAllFace(null).then(
|
_searchService.getAllFace(null).then(
|
||||||
(faceResult) {
|
(locationResult) {
|
||||||
final List<GenericSearchResult> filteredResults = [];
|
final List<GenericSearchResult> filteredResults = [];
|
||||||
for (final result in faceResult) {
|
for (final result in locationResult) {
|
||||||
if (result.name().toLowerCase().contains(query.toLowerCase())) {
|
if (result.name().toLowerCase().contains(query.toLowerCase())) {
|
||||||
filteredResults.add(result);
|
filteredResults.add(result);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@ import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:photos/core/configuration.dart';
|
import 'package:photos/core/configuration.dart';
|
||||||
import 'package:photos/core/error-reporting/super_logging.dart';
|
import 'package:photos/core/error-reporting/super_logging.dart';
|
||||||
import "package:photos/generated/l10n.dart";
|
import "package:photos/generated/l10n.dart";
|
||||||
import "package:photos/ui/common/progress_dialog.dart";
|
|
||||||
import 'package:photos/ui/components/buttons/button_widget.dart';
|
import 'package:photos/ui/components/buttons/button_widget.dart';
|
||||||
import 'package:photos/ui/components/dialog_widget.dart';
|
import 'package:photos/ui/components/dialog_widget.dart';
|
||||||
import 'package:photos/ui/components/models/button_type.dart';
|
import 'package:photos/ui/components/models/button_type.dart';
|
||||||
|
@ -123,28 +122,9 @@ Future<void> _sendLogs(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendLogsForInit(
|
Future<String> getZippedLogsFile(BuildContext context) async {
|
||||||
String toEmail,
|
final dialog = createProgressDialog(context, S.of(context).preparingLogs);
|
||||||
String? subject,
|
await dialog.show();
|
||||||
String? body,
|
|
||||||
) async {
|
|
||||||
final String zipFilePath = await getZippedLogsFile(null);
|
|
||||||
final Email email = Email(
|
|
||||||
recipients: [toEmail],
|
|
||||||
subject: subject ?? '',
|
|
||||||
body: body ?? '',
|
|
||||||
attachmentPaths: [zipFilePath],
|
|
||||||
isHTML: false,
|
|
||||||
);
|
|
||||||
await FlutterEmailSender.send(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> getZippedLogsFile(BuildContext? context) async {
|
|
||||||
late final ProgressDialog dialog;
|
|
||||||
if (context != null) {
|
|
||||||
dialog = createProgressDialog(context, S.of(context).preparingLogs);
|
|
||||||
await dialog.show();
|
|
||||||
}
|
|
||||||
final logsPath = (await getApplicationSupportDirectory()).path;
|
final logsPath = (await getApplicationSupportDirectory()).path;
|
||||||
final logsDirectory = Directory(logsPath + "/logs");
|
final logsDirectory = Directory(logsPath + "/logs");
|
||||||
final tempPath = (await getTemporaryDirectory()).path;
|
final tempPath = (await getTemporaryDirectory()).path;
|
||||||
|
@ -154,9 +134,7 @@ Future<String> getZippedLogsFile(BuildContext? context) async {
|
||||||
encoder.create(zipFilePath);
|
encoder.create(zipFilePath);
|
||||||
await encoder.addDirectory(logsDirectory);
|
await encoder.addDirectory(logsDirectory);
|
||||||
encoder.close();
|
encoder.close();
|
||||||
if (context != null) {
|
await dialog.hide();
|
||||||
await dialog.hide();
|
|
||||||
}
|
|
||||||
return zipFilePath;
|
return zipFilePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
import "package:photos/core/configuration.dart";
|
|
||||||
import "package:photos/db/files_db.dart";
|
|
||||||
|
|
||||||
Future<List<int>> getIndexableFileIDs() async {
|
|
||||||
return FilesDB.instance
|
|
||||||
.getOwnedFileIDs(Configuration.instance.getUserID()!);
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
import "dart:async" show unawaited;
|
|
||||||
|
|
||||||
import "package:wakelock_plus/wakelock_plus.dart";
|
|
||||||
|
|
||||||
class EnteWakeLock {
|
|
||||||
bool _wakeLockEnabledHere = false;
|
|
||||||
|
|
||||||
void enable() {
|
|
||||||
WakelockPlus.enabled.then((value) {
|
|
||||||
if (value == false) {
|
|
||||||
WakelockPlus.enable();
|
|
||||||
//wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
|
|
||||||
//We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
|
|
||||||
_wakeLockEnabledHere = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void disable() {
|
|
||||||
if (_wakeLockEnabledHere) {
|
|
||||||
WakelockPlus.disable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
if (_wakeLockEnabledHere) {
|
|
||||||
unawaited(
|
|
||||||
WakelockPlus.enabled.then((isEnabled) {
|
|
||||||
isEnabled ? WakelockPlus.disable() : null;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<void> toggle({required bool enable}) async {
|
|
||||||
await WakelockPlus.toggle(enable: enable);
|
|
||||||
}
|
|
||||||
}
|
|
277
mobile/plugins/ente_cast/pubspec.lock
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
collection:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.5.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.3.0 <4.0.0"
|
||||||
|
flutter: ">=3.19.0"
|
284
mobile/plugins/ente_cast_none/pubspec.lock
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
|
dio:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.6"
|
||||||
|
ente_cast:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../ente_cast"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.1"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.5.0"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.3.0 <4.0.0"
|
||||||
|
flutter: ">=3.19.0"
|
|
@ -9,14 +9,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "61.0.0"
|
version: "61.0.0"
|
||||||
_flutterfire_internals:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: _flutterfire_internals
|
|
||||||
sha256: "0cb43f83f36ba8cb20502dee0c205e3f3aafb751732d724aeac3f2e044212cc2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.29"
|
|
||||||
adaptive_theme:
|
adaptive_theme:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -577,54 +569,6 @@ packages:
|
||||||
url: "https://github.com/jesims/file_saver.git"
|
url: "https://github.com/jesims/file_saver.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.2.9"
|
version: "0.2.9"
|
||||||
firebase_core:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_core
|
|
||||||
sha256: "6b1152a5af3b1cfe7e45309e96fc1aa14873f410f7aadb3878aa7812acfa7531"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.30.0"
|
|
||||||
firebase_core_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_platform_interface
|
|
||||||
sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.0"
|
|
||||||
firebase_core_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_core_web
|
|
||||||
sha256: c8b02226e548f35aace298e2bb2e6c24e34e8a203d614e742bb1146e5a4ad3c8
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.15.0"
|
|
||||||
firebase_messaging:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: firebase_messaging
|
|
||||||
sha256: "87e3eda0ecdfeadb5fd1cf0dc5153aea5307a0cfca751c4b1ac97bfdd805660e"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "14.8.1"
|
|
||||||
firebase_messaging_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_messaging_platform_interface
|
|
||||||
sha256: "80b4ccf20066b0579ebc88d4678230a5f53ab282fe040e31671af745db1588f9"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.5.31"
|
|
||||||
firebase_messaging_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: firebase_messaging_web
|
|
||||||
sha256: "9224aa4db1ce6f08d96a82978453d37e9980204a20e410a11d9b774b24c6841c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.8.1"
|
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1063,38 +1007,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
in_app_purchase:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: in_app_purchase
|
|
||||||
sha256: def70fbaa2a274f4d835677459f6f7afc5469de912438f86076e51cbd4cbd5b4
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.1.13"
|
|
||||||
in_app_purchase_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: in_app_purchase_android
|
|
||||||
sha256: b9d4ecf70c51ab46222502c050b1535f6249caf9d768c4abd856ea16a18a882d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.3+1"
|
|
||||||
in_app_purchase_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: in_app_purchase_platform_interface
|
|
||||||
sha256: "412efce2b9238c5ace4f057acad43f793ed06880e366d26ae322e796cadb051a"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.7"
|
|
||||||
in_app_purchase_storekit:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: in_app_purchase_storekit
|
|
||||||
sha256: e0f860e760488dbd666e0f27e239d128cba744607fc62434dc76c19d1c292439
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.13+1"
|
|
||||||
integration_test:
|
integration_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
|
@ -12,7 +12,7 @@ description: ente photos application
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
|
|
||||||
version: 0.8.112+636
|
version: 0.8.108+632
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
@ -66,8 +66,6 @@ dependencies:
|
||||||
file_saver:
|
file_saver:
|
||||||
# Use forked version till this PR is merged: https://github.com/incrediblezayed/file_saver/pull/87
|
# Use forked version till this PR is merged: https://github.com/incrediblezayed/file_saver/pull/87
|
||||||
git: https://github.com/jesims/file_saver.git
|
git: https://github.com/jesims/file_saver.git
|
||||||
firebase_core: ^2.30.0
|
|
||||||
firebase_messaging: ^14.8.0
|
|
||||||
fk_user_agent: ^2.0.1
|
fk_user_agent: ^2.0.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
@ -98,7 +96,6 @@ dependencies:
|
||||||
http: ^1.1.0
|
http: ^1.1.0
|
||||||
image: ^4.0.17
|
image: ^4.0.17
|
||||||
image_editor: ^1.3.0
|
image_editor: ^1.3.0
|
||||||
in_app_purchase: ^3.0.7
|
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
json_annotation: ^4.8.0
|
json_annotation: ^4.8.0
|
||||||
latlong2: ^0.9.0
|
latlong2: ^0.9.0
|
||||||
|
@ -203,7 +200,7 @@ flutter_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
adaptive_icon_foreground: "assets/launcher_icon/ente-icon-foreground.png"
|
adaptive_icon_foreground: "assets/launcher_icon/ente-icon-foreground.png"
|
||||||
adaptive_icon_background: "#ffffff"
|
adaptive_icon_background: "#ffffff"
|
||||||
ios: true
|
ios: false # F-Droid
|
||||||
image_path: "assets/icon-light.png"
|
image_path: "assets/icon-light.png"
|
||||||
|
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
|
|
1
mobile/thirdparty/flutter
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit a14f74ff3a1cbd521163c5f03d68113d50af93d3
|
19
mobile/thirdparty/transistor-background-fetch/LICENSE
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2017 Transistor Software <info@transistorsoft.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
22
mobile/thirdparty/transistor-background-fetch/README.md
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
Transistor Background Fetch
|
||||||
|
===========================================================================
|
||||||
|
|
||||||
|
Copyright (c) 2017 Transistor Software <info@transistorsoft.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
28
mobile/thirdparty/transistor-background-fetch/TSBackgroundFetch.podspec
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#
|
||||||
|
# Be sure to run `pod lib lint TSBackgroundFetch.podspec' to ensure this is a
|
||||||
|
# valid spec before submitting.
|
||||||
|
#
|
||||||
|
# Any lines starting with a # are optional, but their use is encouraged
|
||||||
|
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
|
||||||
|
#
|
||||||
|
|
||||||
|
Pod::Spec.new do |s|
|
||||||
|
s.name = 'TSBackgroundFetch'
|
||||||
|
s.version = '0.0.1'
|
||||||
|
s.summary = 'iOS Background Fetch API Manager'
|
||||||
|
|
||||||
|
s.description = <<-DESC
|
||||||
|
iOS Background Fetch API Manager with ability to handle multiple listeners.
|
||||||
|
DESC
|
||||||
|
|
||||||
|
s.homepage = 'http://www.transistorsoft.com'
|
||||||
|
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||||
|
s.author = { 'christocracy' => 'christocracy@gmail.com' }
|
||||||
|
s.source = { :git => 'https://github.com/transistorsoft/transistor-background-fetch.git', :tag => s.version.to_s }
|
||||||
|
s.social_media_url = 'https://twitter.com/christocracy'
|
||||||
|
|
||||||
|
s.ios.deployment_target = '8.0'
|
||||||
|
|
||||||
|
s.source_files = 'ios/TSBackgroundFetch/TSBackgroundFetch/*.{h,m}'
|
||||||
|
s.vendored_frameworks = 'ios/TSBackgroundFetch/TSBackgroundFetch.framework'
|
||||||
|
end
|
9
mobile/thirdparty/transistor-background-fetch/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/libraries
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
1
mobile/thirdparty/transistor-background-fetch/android/app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
28
mobile/thirdparty/transistor-background-fetch/android/app/build.gradle
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "com.transistorsoft.backgroundfetch"
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'androidx.test:runner:1.2.0'
|
||||||
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
|
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
|
||||||
|
}
|
21
mobile/thirdparty/transistor-background-fetch/android/app/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.transistorsoft.backgroundfetch;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.test.InstrumentationRegistry;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() throws Exception {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("com.transistorsoft.backgroundfetch", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
11
mobile/thirdparty/transistor-background-fetch/android/app/src/main/AndroidManifest.xml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.transistorsoft.backgroundfetch">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/AppTheme" />
|
||||||
|
</manifest>
|
|
@ -0,0 +1,34 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="78.5885"
|
||||||
|
android:endY="90.9159"
|
||||||
|
android:startX="48.7653"
|
||||||
|
android:startY="61.0927"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||||
|
android:strokeColor="#00000000"
|
||||||
|
android:strokeWidth="1" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,170 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
|
android:viewportWidth="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#26A69A"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeColor="#33FFFFFF"
|
||||||
|
android:strokeWidth="0.8" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
vendored
Normal file
After Width: | Height: | Size: 3 KiB |
After Width: | Height: | Size: 4.9 KiB |
BIN
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
vendored
Normal file
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 2.8 KiB |
BIN
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
vendored
Normal file
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 6.9 KiB |
BIN
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9 KiB |
After Width: | Height: | Size: 15 KiB |
6
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/colors.xml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
</resources>
|
3
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/strings.xml
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">BackgroundFetch</string>
|
||||||
|
</resources>
|
11
mobile/thirdparty/transistor-background-fetch/android/app/src/main/res/values/styles.xml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">@color/colorPrimary</item>
|
||||||
|
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||||
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.transistorsoft.backgroundfetch;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() throws Exception {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
34
mobile/thirdparty/transistor-background-fetch/android/build.gradle
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||||
|
|
||||||
|
|
||||||
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
// in the individual module build.gradle files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
|
|
||||||
|
ext {
|
||||||
|
compileSdkVersion = 32
|
||||||
|
targetSdkVersion = 31
|
||||||
|
buildToolsVersion = "29.0.6"
|
||||||
|
appCompatVersion = "1.4.1"
|
||||||
|
}
|
23
mobile/thirdparty/transistor-background-fetch/android/gradle.properties
vendored
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Project-wide Gradle settings.
|
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
|
||||||
|
VERSION_NAME=0.5.6
|
||||||
|
VERSION_CODE=21
|
||||||
|
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
BIN
mobile/thirdparty/transistor-background-fetch/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#Thu Jul 15 09:21:17 EDT 2021
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|