diff --git a/.github/assets/github-badge.png b/.github/assets/github-badge.png
deleted file mode 100644
index ef3207c95..000000000
Binary files a/.github/assets/github-badge.png and /dev/null differ
diff --git a/.github/workflows/auth-crowdin.yml b/.github/workflows/auth-crowdin.yml
index f269a8c8d..811def939 100644
--- a/.github/workflows/auth-crowdin.yml
+++ b/.github/workflows/auth-crowdin.yml
@@ -2,12 +2,12 @@ name: "Sync Crowdin translations (auth)"
on:
push:
+ branches: [main]
paths:
# Run workflow when auth's intl_en.arb is changed
- "mobile/lib/l10n/arb/app_en.arb"
# Or the workflow itself is changed
- ".github/workflows/auth-crowdin.yml"
- branches: [main]
schedule:
# See: [Note: Run workflow on specific days of the week]
- cron: "50 1 * * 2,5"
@@ -28,7 +28,7 @@ jobs:
base_path: "auth/"
config: "auth/crowdin.yml"
upload_sources: true
- upload_translations: true
+ upload_translations: false
download_translations: true
localization_branch_name: crowdin-translations-auth
create_pull_request: true
diff --git a/.github/workflows/auth-lint.yml b/.github/workflows/auth-lint.yml
index 1b45a2d32..6504e0646 100644
--- a/.github/workflows/auth-lint.yml
+++ b/.github/workflows/auth-lint.yml
@@ -3,13 +3,13 @@ name: "Lint (auth)"
on:
# Run on every push to a branch other than main that changes auth/
push:
- branches-ignore: [main]
+ branches-ignore: [main, "deploy/**"]
paths:
- "auth/**"
- ".github/workflows/auth-lint.yml"
env:
- FLUTTER_VERSION: "3.16.9"
+ FLUTTER_VERSION: "3.19.3"
jobs:
lint:
diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml
index bade58d25..da888901d 100644
--- a/.github/workflows/auth-release.yml
+++ b/.github/workflows/auth-release.yml
@@ -29,11 +29,11 @@ on:
- "auth-v*"
env:
- FLUTTER_VERSION: "3.13.4"
+ FLUTTER_VERSION: "3.19.3"
jobs:
build-ubuntu:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
defaults:
run:
@@ -72,6 +72,8 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
- name: Build PlayStore AAB
+ # disable this step if release tag contains nightly or beta
+ if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
run: |
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
env:
@@ -83,7 +85,7 @@ jobs:
- name: Install dependencies for desktop build
run: |
sudo apt-get update -y
- sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate
+ sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate appindicator3-0.1 libappindicator3-dev libffi7
- name: Install appimagetool
run: |
@@ -92,8 +94,6 @@ jobs:
mv appimagetool /usr/local/bin/
- name: Build desktop app
- # Temporarily disable desktop builds
- if: false
run: |
flutter config --enable-linux-desktop
dart pub global activate flutter_distributor
@@ -118,6 +118,8 @@ jobs:
updateOnlyUnreleased: true
- name: Upload AAB to PlayStore
+ # disable this step if release tag contains nightly or beta
+ if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
@@ -149,8 +151,6 @@ jobs:
run: mkdir artifacts
- name: Build Windows installer
- # Temporarily disable desktop builds
- if: false
run: |
flutter config --enable-windows-desktop
dart pub global activate flutter_distributor
@@ -159,13 +159,9 @@ jobs:
mv dist/**/ente_auth-*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
- name: Retain Windows EXE and DLLs
- # Temporarily disable desktop builds
- if: false
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
- name: Code sign Windows installer and EXE
- # Temporarily disable desktop builds
- if: false
uses: dlemstra/code-sign-action@v1
with:
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
@@ -175,9 +171,10 @@ jobs:
auth/ente-${{ github.ref_name }}-windows/auth.exe
- name: Zip Windows EXE and DLLs
- # Temporarily disable desktop builds
- if: false
- run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
+ run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows
+
+ - name: Generate checksums
+ run: sha256sum artifacts/ente-* > artifacts/sha256sum-windows
- name: Create a draft GitHub release
uses: ncipollo/release-action@v1
@@ -248,8 +245,6 @@ jobs:
run: mkdir artifacts
- name: Build macOS DMG
- # Temporarily disable desktop builds
- if: false
run: |
flutter config --enable-macos-desktop
dart pub global activate flutter_distributor
@@ -257,16 +252,12 @@ jobs:
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
- name: Code sign DMG
- # Temporarily disable desktop builds
- if: false
run: |
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
codesign --force --timestamp --sign "$CERT_NAME" --options runtime artifacts/ente-${{ github.ref_name }}.dmg
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
- name: Notarize and staple DMG
- # Temporarily disable desktop builds
- if: false
run: |
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
--wait \
@@ -279,6 +270,9 @@ jobs:
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ - name: Generate checksums
+ run: shasum -a 256 artifacts/ente-* > artifacts/sha256sum-macos
+
- name: Create a draft GitHub release
uses: ncipollo/release-action@v1
with:
diff --git a/.github/workflows/copycat-db-release.yaml b/.github/workflows/copycat-db-release.yaml
new file mode 100644
index 000000000..5ec942879
--- /dev/null
+++ b/.github/workflows/copycat-db-release.yaml
@@ -0,0 +1,24 @@
+name: "Release (copycat-db)"
+
+on:
+ workflow_dispatch: # Run manually
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ name: Check out code
+
+ - uses: mr-smithers-excellent/docker-build-push@v6
+ name: Build & Push
+ with:
+ dockerfile: infra/copycat-db/Dockerfile
+ directory: infra/copycat-db
+ image: ente/copycat-db
+ registry: rg.fr-par.scw.cloud
+ enableBuildKit: true
+ buildArgs: GIT_COMMIT=${GITHUB_SHA}
+ tags: ${GITHUB_SHA}, latest
+ username: ${{ secrets.DOCKER_USERNAME }}
+ password: ${{ secrets.DOCKER_PASSWORD }}
diff --git a/.github/workflows/docs-verify-build.yml b/.github/workflows/docs-verify-build.yml
index addb52a05..a57f71c86 100644
--- a/.github/workflows/docs-verify-build.yml
+++ b/.github/workflows/docs-verify-build.yml
@@ -6,7 +6,7 @@ name: "Verify build (docs)"
on:
# Run on every push to a branch other than main that changes docs/
push:
- branches-ignore: [main]
+ branches-ignore: [main, "deploy/**"]
paths:
- "docs/**"
- ".github/workflows/docs-verify-build.yml"
diff --git a/.github/workflows/mobile-crowdin.yml b/.github/workflows/mobile-crowdin.yml
index 35b4c3876..5c52b59ad 100644
--- a/.github/workflows/mobile-crowdin.yml
+++ b/.github/workflows/mobile-crowdin.yml
@@ -2,12 +2,12 @@ name: "Sync Crowdin translations (mobile)"
on:
push:
+ branches: [main]
paths:
# Run workflow when mobiles's intl_en.arb is changed
- "mobile/lib/l10n/intl_en.arb"
# Or the workflow itself is changed
- ".github/workflows/mobile-crowdin.yml"
- branches: [main]
schedule:
# See: [Note: Run workflow on specific days of the week]
- cron: "40 1 * * 2,5"
@@ -28,7 +28,7 @@ jobs:
base_path: "mobile/"
config: "mobile/crowdin.yml"
upload_sources: true
- upload_translations: true
+ upload_translations: false
download_translations: true
localization_branch_name: crowdin-translations-mobile
create_pull_request: true
diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml
index cbbbbcfbb..48e38dc6c 100644
--- a/.github/workflows/mobile-lint.yml
+++ b/.github/workflows/mobile-lint.yml
@@ -3,7 +3,7 @@ name: "Lint (mobile)"
on:
# Run on every push to a branch other than main that changes mobile/
push:
- branches-ignore: [main, f-droid]
+ branches-ignore: [main, f-droid, "deploy/**"]
paths:
- "mobile/**"
- ".github/workflows/mobile-lint.yml"
diff --git a/.github/workflows/server-lint.yml b/.github/workflows/server-lint.yml
index c051d0290..d25f2adcc 100644
--- a/.github/workflows/server-lint.yml
+++ b/.github/workflows/server-lint.yml
@@ -3,7 +3,7 @@ name: "Lint (server)"
on:
# Run on every push to a branch other than main that changes server/
push:
- branches-ignore: [main]
+ branches-ignore: [main, "deploy/**"]
paths:
- "server/**"
- ".github/workflows/server-lint.yml"
diff --git a/.github/workflows/server-publish.yml b/.github/workflows/server-publish.yml
new file mode 100644
index 000000000..393fa899d
--- /dev/null
+++ b/.github/workflows/server-publish.yml
@@ -0,0 +1,38 @@
+name: "Publish (server)"
+
+on:
+ # Run manually, providing it the commit.
+ #
+ # To obtain the commit from the currently deployed museum, do:
+ # curl -s https://api.ente.io/ping | jq -r '.id'
+ #
+ # See server/docs/publish.md for more details.
+ workflow_dispatch:
+ inputs:
+ commit:
+ description: "Commit to publish the image from"
+ type: string
+ required: true
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.commit }}
+
+ - name: Build and push
+ uses: mr-smithers-excellent/docker-build-push@v6
+ with:
+ dockerfile: server/Dockerfile
+ directory: server
+ # Resultant package name will be ghcr.io/ente-io/server
+ image: server
+ registry: ghcr.io
+ enableBuildKit: true
+ buildArgs: GIT_COMMIT=${{ inputs.commit }}
+ tags: ${{ inputs.commit }}, latest
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/server-release.yml b/.github/workflows/server-release.yml
index 8f0281951..fa0215530 100644
--- a/.github/workflows/server-release.yml
+++ b/.github/workflows/server-release.yml
@@ -7,11 +7,11 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
- name: Check out code
+ - name: Checkout code
+ uses: actions/checkout@v4
- - uses: mr-smithers-excellent/docker-build-push@v6
- name: Build & Push
+ - name: Build and push
+ uses: mr-smithers-excellent/docker-build-push@v6
with:
dockerfile: server/Dockerfile
directory: server
diff --git a/.github/workflows/web-crowdin.yml b/.github/workflows/web-crowdin.yml
index 8733167d6..f834e62f3 100644
--- a/.github/workflows/web-crowdin.yml
+++ b/.github/workflows/web-crowdin.yml
@@ -2,12 +2,12 @@ name: "Sync Crowdin translations (web)"
on:
push:
+ branches: [main]
paths:
# Run workflow when web's en-US/translation.json is changed
- "web/apps/photos/public/locales/en-US/translation.json"
# Or the workflow itself is changed
- ".github/workflows/web-crowdin.yml"
- branches: [main]
schedule:
# [Note: Run workflow on specific days of the week]
#
@@ -34,7 +34,7 @@ jobs:
base_path: "web/"
config: "web/crowdin.yml"
upload_sources: true
- upload_translations: true
+ upload_translations: false
download_translations: true
localization_branch_name: crowdin-translations-web
create_pull_request: true
diff --git a/.github/workflows/web-deploy-payments.yml b/.github/workflows/web-deploy-payments.yml
new file mode 100644
index 000000000..c428d88bc
--- /dev/null
+++ b/.github/workflows/web-deploy-payments.yml
@@ -0,0 +1,43 @@
+name: "Deploy (payments)"
+
+on:
+ push:
+ # Run workflow on pushes to the deploy/payments
+ branches: [deploy/payments]
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ defaults:
+ run:
+ working-directory: web
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Setup node and enable yarn caching
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: "yarn"
+ cache-dependency-path: "docs/yarn.lock"
+
+ - name: Install dependencies
+ run: yarn install
+
+ - name: Build payments
+ run: yarn build:payments
+
+ - name: Publish payments
+ uses: cloudflare/pages-action@1
+ with:
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ projectName: ente
+ branch: deploy/payments
+ directory: web/apps/payments/out
+ wranglerVersion: "3"
diff --git a/.github/workflows/web-lint.yml b/.github/workflows/web-lint.yml
index 7f5d27002..0dc11aa0e 100644
--- a/.github/workflows/web-lint.yml
+++ b/.github/workflows/web-lint.yml
@@ -3,7 +3,7 @@ name: "Lint (web)"
on:
# Run on every push to a branch other than main that changes web/
push:
- branches-ignore: [main]
+ branches-ignore: [main, "deploy/**"]
paths:
- "web/**"
- ".github/workflows/web-lint.yml"
diff --git a/.github/workflows/web-nightly.yml b/.github/workflows/web-nightly.yml
index a800a4b73..89d5ecaa5 100644
--- a/.github/workflows/web-nightly.yml
+++ b/.github/workflows/web-nightly.yml
@@ -78,6 +78,19 @@ jobs:
directory: web/apps/cast/out
wranglerVersion: "3"
+ - name: Build payments
+ run: yarn build:payments
+
+ - name: Publish payments
+ uses: cloudflare/pages-action@1
+ with:
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ projectName: ente
+ branch: n-payments
+ directory: web/apps/payments/out
+ wranglerVersion: "3"
+
- name: Build photos
run: yarn build:photos
env:
diff --git a/.github/workflows/web-preview.yml b/.github/workflows/web-preview.yml
index 4e86d9a81..2ad73b7a1 100644
--- a/.github/workflows/web-preview.yml
+++ b/.github/workflows/web-preview.yml
@@ -12,6 +12,7 @@ on:
- "accounts"
- "auth"
- "cast"
+ - "payments"
- "photos"
jobs:
diff --git a/README.md b/README.md
index 68632ea3b..88488d11c 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ existing users will be grandfathered in.
[](https://apps.apple.com/app/id6444121398)
[](https://play.google.com/store/apps/details?id=io.ente.auth)
[](https://f-droid.org/packages/io.ente.auth/)
-[](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
+[](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
[](https://auth.ente.io)
diff --git a/auth/.gitignore b/auth/.gitignore
index 3d6f77b84..87abd6a1c 100644
--- a/auth/.gitignore
+++ b/auth/.gitignore
@@ -18,6 +18,11 @@
*.iws
.idea/
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
@@ -35,3 +40,4 @@ lib/generated_plugin_registrant.dart
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
android/key.properties
+dist/
\ No newline at end of file
diff --git a/auth/.metadata b/auth/.metadata
index 42069fa1b..39ee784e4 100644
--- a/auth/.metadata
+++ b/auth/.metadata
@@ -1,11 +1,11 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
-# This file should be version controlled.
+# This file should be version controlled and should not be manually edited.
version:
- revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- channel: unknown
+ revision: "ba393198430278b6595976de84fe170f553cc728"
+ channel: "[user-branch]"
project_type: app
@@ -13,17 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
+ - platform: android
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
+ - platform: ios
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: linux
- create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: macos
- create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
+ - platform: web
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: windows
- create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
+ create_revision: ba393198430278b6595976de84fe170f553cc728
+ base_revision: ba393198430278b6595976de84fe170f553cc728
# User provided section
diff --git a/auth/README.md b/auth/README.md
index 71ae1f93b..e2e03f023 100644
--- a/auth/README.md
+++ b/auth/README.md
@@ -31,14 +31,16 @@ You can alternatively install the build from PlayStore or F-Droid.
+### Desktop
+
+You can [**download**](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
+a native desktop app from this repository's GitHub releases. The desktop app
+works on Windows, Linux and macOS.
+
### Web
You can view your 2FA codes at [auth.ente.io](https://auth.ente.io). For adding
-or managing your secrets, please use our mobile app.
-
-### Desktop
-
-A native desktop app is coming soon!
+or managing your secrets, please use our mobile or desktop app.
## 🧑💻 Build from source
diff --git a/auth/android/app/build.gradle b/auth/android/app/build.gradle
index 916e3b3c9..5621b08b6 100644
--- a/auth/android/app/build.gradle
+++ b/auth/android/app/build.gradle
@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -46,7 +46,7 @@ android {
defaultConfig {
applicationId "io.ente.auth"
- minSdkVersion 20
+ minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -56,11 +56,11 @@ android {
signingConfigs {
release {
- storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
- keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
- keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
- storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
- }
+ storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
+ keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
+ keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
+ storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
+ }
}
flavorDimensions "default"
@@ -109,6 +109,7 @@ dependencies {
implementation 'io.sentry:sentry-android:2.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3'
+ implementation 'com.google.guava:guava:28.2-android'
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json
index b248ad8cb..3e8f8ce67 100644
--- a/auth/assets/custom-icons/_data/custom-icons.json
+++ b/auth/assets/custom-icons/_data/custom-icons.json
@@ -36,7 +36,9 @@
},
{
"title": "BorgBase",
- "altNames": ["borg"],
+ "altNames": [
+ "borg"
+ ],
"slug": "BorgBase"
},
{
@@ -46,11 +48,17 @@
{
"title": "Bybit"
},
+ {
+ "title": "CERN"
+ },
{
"title": "Channel Island Hosting",
"slug": "cih",
"hex": "D14633"
},
+ {
+ "title": "ConfigCat"
+ },
{
"title": "Cloudflare"
},
@@ -62,6 +70,13 @@
{
"title": "Crowdpear"
},
+ {
+ "title": "DCS",
+ "altNames": [
+ "Digital Combat Simulator"
+ ],
+ "slug": "dcs"
+ },
{
"title": "DEGIRO"
},
@@ -107,9 +122,14 @@
},
{
"title": "Gosuslugi",
- "altNames": ["Госуслуги"],
+ "altNames": [
+ "Госуслуги"
+ ],
"slug": "Gosuslugi"
},
+ {
+ "title": "Habbo"
+ },
{
"title": "Healthchecks.io",
"slug": "healthchecks"
@@ -172,13 +192,24 @@
},
{
"title": "Mastodon",
- "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
+ "altNames": [
+ "mstdn",
+ "fediscience",
+ "mathstodon",
+ "fosstodon"
+ ],
"slug": "mastodon",
"hex": "6364FF"
},
+ {
+ "title": "Mercado Livre",
+ "slug": "mercado_livre"
+ },
{
"title": "Murena",
- "altNames": ["eCloud"],
+ "altNames": [
+ "eCloud"
+ ],
"slug": "ecloud"
},
{
@@ -267,11 +298,18 @@
"title": "Revolt",
"hex": "858585"
},
+ {
+ "title": "Rockstar Games",
+ "slug": "rockstar_games"
+ },
{
"title": "Rust Language Forum",
"slug": "rust_language_forum",
"hex": "000000"
},
+ {
+ "title": "Sendgrid"
+ },
{
"title": "service-bw"
},
@@ -356,15 +394,24 @@
{
"title": "Wise"
},
+ {
+ "title": "WYZE",
+ "slug": "wyze"
+ },
{
"title": "X",
- "altNames": ["twitter"],
+ "altNames": [
+ "twitter"
+ ],
"slug": "x"
},
{
"title": "Yandex",
- "altNames": ["Ya", "Яндекс"],
+ "altNames": [
+ "Ya",
+ "Яндекс"
+ ],
"slug": "Yandex"
}
]
-}
+}
\ No newline at end of file
diff --git a/auth/assets/custom-icons/icons/CERN.svg b/auth/assets/custom-icons/icons/CERN.svg
new file mode 100644
index 000000000..0e4b82e1f
--- /dev/null
+++ b/auth/assets/custom-icons/icons/CERN.svg
@@ -0,0 +1,47 @@
+
+
+
diff --git a/auth/assets/custom-icons/icons/configcat.svg b/auth/assets/custom-icons/icons/configcat.svg
new file mode 100644
index 000000000..cfecd22b0
--- /dev/null
+++ b/auth/assets/custom-icons/icons/configcat.svg
@@ -0,0 +1,9 @@
+
diff --git a/auth/assets/custom-icons/icons/dcs.svg b/auth/assets/custom-icons/icons/dcs.svg
new file mode 100644
index 000000000..dd7c41ccb
--- /dev/null
+++ b/auth/assets/custom-icons/icons/dcs.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/auth/assets/custom-icons/icons/habbo.svg b/auth/assets/custom-icons/icons/habbo.svg
new file mode 100644
index 000000000..746bcdb22
--- /dev/null
+++ b/auth/assets/custom-icons/icons/habbo.svg
@@ -0,0 +1,9 @@
+
diff --git a/auth/assets/custom-icons/icons/mercado_livre.svg b/auth/assets/custom-icons/icons/mercado_livre.svg
new file mode 100644
index 000000000..7f4db5fd5
--- /dev/null
+++ b/auth/assets/custom-icons/icons/mercado_livre.svg
@@ -0,0 +1,9 @@
+
diff --git a/auth/assets/custom-icons/icons/rockstar_games.svg b/auth/assets/custom-icons/icons/rockstar_games.svg
new file mode 100644
index 000000000..bf2061c2b
--- /dev/null
+++ b/auth/assets/custom-icons/icons/rockstar_games.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/auth/assets/custom-icons/icons/sendgrid.svg b/auth/assets/custom-icons/icons/sendgrid.svg
new file mode 100644
index 000000000..1562adab9
--- /dev/null
+++ b/auth/assets/custom-icons/icons/sendgrid.svg
@@ -0,0 +1,9 @@
+
diff --git a/auth/assets/custom-icons/icons/wyze.svg b/auth/assets/custom-icons/icons/wyze.svg
new file mode 100644
index 000000000..89d252c15
--- /dev/null
+++ b/auth/assets/custom-icons/icons/wyze.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/auth/assets/icon-light-adaptive-fg.png b/auth/assets/generation-icons/icon-light-adaptive-fg.png
similarity index 100%
rename from auth/assets/icon-light-adaptive-fg.png
rename to auth/assets/generation-icons/icon-light-adaptive-fg.png
diff --git a/auth/assets/icon-light.png b/auth/assets/generation-icons/icon-light.png
similarity index 100%
rename from auth/assets/icon-light.png
rename to auth/assets/generation-icons/icon-light.png
diff --git a/auth/assets/icons/auth-icon.ico b/auth/assets/icons/auth-icon.ico
new file mode 100644
index 000000000..38bb22bcf
Binary files /dev/null and b/auth/assets/icons/auth-icon.ico differ
diff --git a/auth/assets/icons/auth-icon.png b/auth/assets/icons/auth-icon.png
new file mode 100644
index 000000000..3db75740b
Binary files /dev/null and b/auth/assets/icons/auth-icon.png differ
diff --git a/auth/distribute_options.yaml b/auth/distribute_options.yaml
new file mode 100644
index 000000000..9634f4cbd
--- /dev/null
+++ b/auth/distribute_options.yaml
@@ -0,0 +1,25 @@
+output: dist/
+
+releases:
+ - name: dev
+ jobs:
+ - name: release-dev-linux-zip
+ package:
+ platform: linux
+ target: zip
+ - name: release-dev-linux-deb
+ package:
+ platform: linux
+ target: deb
+ - name: release-dev-linux-appimage
+ package:
+ platform: linux
+ target: appimage
+ - name: release-dev-windows-exe
+ package:
+ platform: windows
+ target: exe
+ - name: release-dev-macos-dmg
+ package:
+ platform: macos
+ target: dmg
diff --git a/auth/fastlane/metadata/android/en-US/full_description.txt b/auth/fastlane/metadata/android/en-US/full_description.txt
index fbab88c75..0db377761 100644
--- a/auth/fastlane/metadata/android/en-US/full_description.txt
+++ b/auth/fastlane/metadata/android/en-US/full_description.txt
@@ -37,4 +37,4 @@ file, that adheres to the above format.
SUPPORT
If you need help, please reach out to support@ente.io, and a human will get in touch with you.
-If you have feature requests, please create an issue @ https://github.com/ente-io/auth
+If you have feature requests, please create an issue @ https://github.com/ente-io/ente
diff --git a/auth/fastlane/metadata/android/en-US/title.txt b/auth/fastlane/metadata/android/en-US/title.txt
index f1c54762a..257e3ddcc 100644
--- a/auth/fastlane/metadata/android/en-US/title.txt
+++ b/auth/fastlane/metadata/android/en-US/title.txt
@@ -1 +1 @@
-ente Authenticator
\ No newline at end of file
+Ente Authenticator
\ No newline at end of file
diff --git a/auth/fdroid_flutter_icons.yaml b/auth/fdroid_flutter_icons.yaml
index da327160a..0ef87effd 100644
--- a/auth/fdroid_flutter_icons.yaml
+++ b/auth/fdroid_flutter_icons.yaml
@@ -1,6 +1,6 @@
-flutter_icons:
- android: "launcher_icon"
- image_path: "assets/icon-light.png"
- adaptive_icon_foreground: "assets/icon-light-adaptive-fg.png"
- adaptive_icon_background: "#ffffff"
-
+flutter_icons:
+ android: "launcher_icon"
+ image_path: "assets/generation-icons/icon-light.png"
+ adaptive_icon_foreground: "assets/generation-icons/icon-light-adaptive-fg.png"
+ adaptive_icon_background: "#ffffff"
+
diff --git a/auth/ios/Podfile.lock b/auth/ios/Podfile.lock
index 9e781fd48..aec7e14f0 100644
--- a/auth/ios/Podfile.lock
+++ b/auth/ios/Podfile.lock
@@ -1,7 +1,9 @@
PODS:
- - connectivity (0.0.1):
+ - app_links (0.0.1):
- Flutter
- - Reachability
+ - connectivity_plus (0.0.1):
+ - Flutter
+ - ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.4):
@@ -45,27 +47,26 @@ PODS:
- Flutter (1.0.0)
- flutter_email_sender (0.0.1):
- Flutter
- - flutter_inappwebview (0.0.1):
+ - flutter_inappwebview_ios (0.0.1):
- Flutter
- - flutter_inappwebview/Core (= 0.0.1)
+ - flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- - flutter_inappwebview/Core (0.0.1):
+ - flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
+ - flutter_local_authentication (1.2.0):
+ - Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- - flutter_sodium (0.0.1):
- - Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- - FMDB (2.7.5):
- - FMDB/standard (= 2.7.5)
- - FMDB/standard (2.7.5)
+ - local_auth_darwin (0.0.1):
+ - Flutter
- local_auth_ios (0.0.1):
- Flutter
- move_to_background (0.0.1):
@@ -82,46 +83,65 @@ PODS:
- qr_code_scanner (0.2.0):
- Flutter
- MTBBarcodeScanner
- - Reachability (3.2)
- - SDWebImage (5.17.0):
- - SDWebImage/Core (= 5.17.0)
- - SDWebImage/Core (5.17.0)
- - Sentry/HybridSDK (8.9.1):
- - SentryPrivate (= 8.9.1)
+ - ReachabilitySwift (5.2.1)
+ - SDWebImage (5.19.0):
+ - SDWebImage/Core (= 5.19.0)
+ - SDWebImage/Core (5.19.0)
+ - Sentry/HybridSDK (8.21.0):
+ - SentryPrivate (= 8.21.0)
- sentry_flutter (0.0.1):
- Flutter
- FlutterMacOS
- - Sentry/HybridSDK (= 8.9.1)
- - SentryPrivate (8.9.1)
+ - Sentry/HybridSDK (= 8.21.0)
+ - SentryPrivate (8.21.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
+ - smart_auth (0.0.1):
+ - Flutter
+ - sodium_libs (2.2.1):
+ - Flutter
- sqflite (0.0.3):
- Flutter
- - FMDB (>= 2.7.5)
- - SwiftyGif (5.4.4)
- - Toast (4.0.0)
- - uni_links (0.0.1):
+ - FlutterMacOS
+ - sqlite3 (3.45.1):
+ - sqlite3/common (= 3.45.1)
+ - sqlite3/common (3.45.1)
+ - sqlite3/fts5 (3.45.1):
+ - sqlite3/common
+ - sqlite3/perf-threadsafe (3.45.1):
+ - sqlite3/common
+ - sqlite3/rtree (3.45.1):
+ - sqlite3/common
+ - sqlite3_flutter_libs (0.0.1):
- Flutter
+ - sqlite3 (~> 3.45.1)
+ - sqlite3/fts5
+ - sqlite3/perf-threadsafe
+ - sqlite3/rtree
+ - SwiftyGif (5.4.4)
+ - Toast (4.1.0)
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- - connectivity (from `.symlinks/plugins/connectivity/ios`)
+ - app_links (from `.symlinks/plugins/app_links/ios`)
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`)
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
- Flutter (from `Flutter`)
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
- - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
+ - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
+ - flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- - flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
+ - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
@@ -131,27 +151,31 @@ DEPENDENCIES:
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqflite (from `.symlinks/plugins/sqflite/ios`)
- - uni_links (from `.symlinks/plugins/uni_links/ios`)
+ - smart_auth (from `.symlinks/plugins/smart_auth/ios`)
+ - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
+ - sqflite (from `.symlinks/plugins/sqflite/darwin`)
+ - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- - FMDB
- MTBBarcodeScanner
- OrderedSet
- - Reachability
+ - ReachabilitySwift
- SDWebImage
- Sentry
- SentryPrivate
+ - sqlite3
- SwiftyGif
- Toast
EXTERNAL SOURCES:
- connectivity:
- :path: ".symlinks/plugins/connectivity/ios"
+ app_links:
+ :path: ".symlinks/plugins/app_links/ios"
+ connectivity_plus:
+ :path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
@@ -164,18 +188,20 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_email_sender:
:path: ".symlinks/plugins/flutter_email_sender/ios"
- flutter_inappwebview:
- :path: ".symlinks/plugins/flutter_inappwebview/ios"
+ flutter_inappwebview_ios:
+ :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
+ flutter_local_authentication:
+ :path: ".symlinks/plugins/flutter_local_authentication/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
- flutter_sodium:
- :path: ".symlinks/plugins/flutter_sodium/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
+ local_auth_darwin:
+ :path: ".symlinks/plugins/local_auth_darwin/darwin"
local_auth_ios:
:path: ".symlinks/plugins/local_auth_ios/ios"
move_to_background:
@@ -194,50 +220,58 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ smart_auth:
+ :path: ".symlinks/plugins/smart_auth/ios"
+ sodium_libs:
+ :path: ".symlinks/plugins/sodium_libs/ios"
sqflite:
- :path: ".symlinks/plugins/sqflite/ios"
- uni_links:
- :path: ".symlinks/plugins/uni_links/ios"
+ :path: ".symlinks/plugins/sqflite/darwin"
+ sqlite3_flutter_libs:
+ :path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
- connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
- device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
+ app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
+ connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
+ device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
- file_picker: ce3938a0df3cc1ef404671531facef740d03f920
+ file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
- Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
- flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
- flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
+ flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
+ flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
+ flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
- flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
- FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
- local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
+ local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
+ local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
- package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
- path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
+ package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
+ path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
- Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
- SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
- Sentry: e3203780941722a1fcfee99e351de14244c7f806
- sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
- SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
+ ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
+ SDWebImage: 981fd7e860af070920f249fd092420006014c3eb
+ Sentry: ebc12276bd17613a114ab359074096b6b3725203
+ sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
+ SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
- shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
- sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
+ shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
+ smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
+ sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
+ sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
+ sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
+ sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
- Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
- uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
- url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
+ Toast: ec33c32b8688982cecc6348adeae667c1b9938da
+ url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
diff --git a/auth/ios/Runner.xcodeproj/project.pbxproj b/auth/ios/Runner.xcodeproj/project.pbxproj
index 219a8441c..4d77182b0 100644
--- a/auth/ios/Runner.xcodeproj/project.pbxproj
+++ b/auth/ios/Runner.xcodeproj/project.pbxproj
@@ -159,7 +159,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1430;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
diff --git a/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a6b826db2..5e31d3d34 100644
--- a/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
Bool {
GeneratedPluginRegistrant.register(with: self)
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+
+ super.application(application, didFinishLaunchingWithOptions: launchOptions)
+
+ if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
+ AppLinks.shared.handleLink(url: url)
+ }
+
+ return false
+
+ // return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
diff --git a/auth/ios/Runner/Info.plist b/auth/ios/Runner/Info.plist
index f3e756539..35921ba0c 100644
--- a/auth/ios/Runner/Info.plist
+++ b/auth/ios/Runner/Info.plist
@@ -78,5 +78,9 @@
UIViewControllerBasedStatusBarAppearance
+ LSSupportsOpeningDocumentsInPlace
+
+ UIFileSharingEnabled
+
diff --git a/auth/lib/app/view/app.dart b/auth/lib/app/view/app.dart
index b723e54b9..3bd9d4e73 100644
--- a/auth/lib/app/view/app.dart
+++ b/auth/lib/app/view/app.dart
@@ -12,15 +12,18 @@ import 'package:ente_auth/locale.dart';
import "package:ente_auth/onboarding/view/onboarding_page.dart";
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/services/user_service.dart';
+import 'package:ente_auth/services/window_listener_service.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
import 'package:flutter/foundation.dart';
import "package:flutter/material.dart";
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:tray_manager/tray_manager.dart';
+import 'package:window_manager/window_manager.dart';
class App extends StatefulWidget {
final Locale locale;
- const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
+ const App({super.key, this.locale = const Locale("en")});
static void setLocale(BuildContext context, Locale newLocale) {
_AppState state = context.findAncestorStateOfType<_AppState>()!;
@@ -31,7 +34,7 @@ class App extends StatefulWidget {
State createState() => _AppState();
}
-class _AppState extends State {
+class _AppState extends State with WindowListener, TrayListener {
late StreamSubscription _signedOutEvent;
late StreamSubscription _signedInEvent;
Locale? locale;
@@ -41,8 +44,19 @@ class _AppState extends State {
});
}
+ Future initWindowManager() async {
+ windowManager.addListener(this);
+ }
+
+ Future initTrayManager() async {
+ trayManager.addListener(this);
+ }
+
@override
void initState() {
+ initWindowManager();
+ initTrayManager();
+
_signedOutEvent = Bus.instance.on().listen((event) {
if (mounted) {
setState(() {});
@@ -76,6 +90,10 @@ class _AppState extends State {
@override
void dispose() {
super.dispose();
+
+ windowManager.removeListener(this);
+ trayManager.removeListener(this);
+
_signedOutEvent.cancel();
_signedInEvent.cancel();
}
@@ -134,4 +152,45 @@ class _AppState extends State {
: const OnboardingPage(),
};
}
+
+ @override
+ void onWindowResize() {
+ WindowListenerService.instance.onWindowResize().ignore();
+ }
+
+ @override
+ void onTrayIconMouseDown() {
+ if (Platform.isWindows) {
+ windowManager.show();
+ } else {
+ trayManager.popUpContextMenu();
+ }
+ }
+
+ @override
+ void onTrayIconRightMouseDown() {
+ if (Platform.isWindows) {
+ trayManager.popUpContextMenu();
+ } else {
+ windowManager.show();
+ }
+ }
+
+ @override
+ void onTrayIconRightMouseUp() {}
+
+ @override
+ void onTrayMenuItemClick(MenuItem menuItem) {
+ switch (menuItem.key) {
+ case 'hide_window':
+ windowManager.hide();
+ break;
+ case 'show_window':
+ windowManager.show();
+ break;
+ case 'exit_app':
+ windowManager.close();
+ break;
+ }
+ }
}
diff --git a/auth/lib/app/view/app_route.dart b/auth/lib/app/view/app_route.dart
deleted file mode 100644
index b3cf7bb42..000000000
--- a/auth/lib/app/view/app_route.dart
+++ /dev/null
@@ -1,4 +0,0 @@
-class AppRoute {
- static const String enterSecretKeyPage = "enterSecretKeyPage";
- static const String settings = "settings";
-}
diff --git a/auth/lib/app/view/app_theme_extension.dart b/auth/lib/app/view/app_theme_extension.dart
deleted file mode 100644
index 548be3d4f..000000000
--- a/auth/lib/app/view/app_theme_extension.dart
+++ /dev/null
@@ -1,163 +0,0 @@
-import "package:flutter/material.dart";
-
-final lightTheme = ThemeData(
- fontFamily: "Inter",
- brightness: Brightness.light,
- scaffoldBackgroundColor: Colors.white,
- appBarTheme: const AppBarTheme().copyWith(
- backgroundColor: Colors.white,
- foregroundColor: Colors.black,
- iconTheme: const IconThemeData(color: Colors.black),
- elevation: 0,
- ),
- colorScheme: const ColorScheme.light(
- primary: Colors.black,
- ),
- textTheme: _buildTextTheme(Colors.black),
- outlinedButtonTheme: buildOutlinedButtonThemeData(
- bgDisabled: Colors.grey.shade500,
- bgEnabled: Colors.black,
- fgDisabled: Colors.white,
- fgEnabled: Colors.white,
- ),
- inputDecorationTheme: InputDecorationTheme(
- fillColor: null,
- filled: true,
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 14,
- ),
- border: UnderlineInputBorder(
- borderSide: BorderSide.none,
- borderRadius: BorderRadius.circular(6),
- ),
- ),
-);
-
-final darkTheme = ThemeData(
- fontFamily: "Inter",
- brightness: Brightness.dark,
- scaffoldBackgroundColor: Colors.black,
- appBarTheme: const AppBarTheme(color: Colors.orange),
- textTheme: _buildTextTheme(Colors.white),
- outlinedButtonTheme: buildOutlinedButtonThemeData(
- bgDisabled: Colors.grey.shade500,
- bgEnabled: Colors.white,
- fgDisabled: Colors.white,
- fgEnabled: Colors.black,
- ),
- inputDecorationTheme: InputDecorationTheme(
- fillColor: null,
- filled: true,
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 14,
- ),
- border: UnderlineInputBorder(
- borderSide: BorderSide.none,
- borderRadius: BorderRadius.circular(6),
- ),
- ), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: Colors.black),
-);
-
-TextTheme _buildTextTheme(Color textColor) {
- return const TextTheme().copyWith(
- headlineMedium: TextStyle(
- color: textColor,
- fontSize: 32,
- fontWeight: FontWeight.w700,
- fontFamily: "Inter",
- ),
- headlineSmall: TextStyle(
- color: textColor,
- fontSize: 24,
- fontWeight: FontWeight.w600,
- fontFamily: "Inter",
- ),
- // AG: Body
- titleLarge: TextStyle(
- color: textColor,
- fontSize: 18,
- fontFamily: "Inter",
- fontWeight: FontWeight.w500,
- ),
- // Use labels for buttons or notifications
- labelMedium: TextStyle(
- color: textColor,
- fontFamily: "Inter",
- fontSize: 18,
- fontWeight: FontWeight.w700,
- height: 28,
- ),
-
- titleMedium: TextStyle(
- color: textColor,
- fontFamily: "Inter",
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- titleSmall: TextStyle(
- color: textColor,
- fontFamily: "Inter",
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- bodyLarge: TextStyle(
- fontFamily: "Inter",
- color: textColor,
- fontSize: 16,
- fontWeight: FontWeight.w500,
- ),
- bodyMedium: TextStyle(
- fontFamily: "Inter",
- color: textColor,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- bodySmall: TextStyle(
- color: textColor.withOpacity(0.6),
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- );
-}
-
-OutlinedButtonThemeData buildOutlinedButtonThemeData({
- required Color bgDisabled,
- required Color bgEnabled,
- required Color fgDisabled,
- required Color fgEnabled,
-}) {
- return OutlinedButtonThemeData(
- style: OutlinedButton.styleFrom(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(8),
- ),
- alignment: Alignment.center,
- padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 50),
- textStyle: const TextStyle(
- fontWeight: FontWeight.w700,
- fontFamily: "Inter",
- fontSize: 18,
- ),
- ).copyWith(
- backgroundColor: MaterialStateProperty.resolveWith(
- (Set states) {
- if (states.contains(MaterialState.disabled)) {
- return bgDisabled;
- }
- return bgEnabled;
- },
- ),
- foregroundColor: MaterialStateProperty.resolveWith(
- (Set states) {
- if (states.contains(MaterialState.disabled)) {
- return fgDisabled;
- }
- return fgEnabled;
- },
- ),
- alignment: Alignment.center,
- ),
- );
-}
diff --git a/auth/lib/bootstrap.dart b/auth/lib/bootstrap.dart
index e423ba7c8..5f774e397 100644
--- a/auth/lib/bootstrap.dart
+++ b/auth/lib/bootstrap.dart
@@ -1,3 +1,5 @@
+// ignore_for_file: deprecated_member_use
+
import "dart:async";
import "dart:developer";
diff --git a/auth/lib/core/configuration.dart b/auth/lib/core/configuration.dart
index da3ad9ebf..096fe91b2 100644
--- a/auth/lib/core/configuration.dart
+++ b/auth/lib/core/configuration.dart
@@ -13,12 +13,12 @@ import 'package:ente_auth/models/key_attributes.dart';
import 'package:ente_auth/models/key_gen_result.dart';
import 'package:ente_auth/models/private_key_attributes.dart';
import 'package:ente_auth/store/authenticator_db.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:tuple/tuple.dart';
class Configuration {
@@ -72,9 +72,10 @@ class Configuration {
Future init() async {
_preferences = await SharedPreferences.getInstance();
+ sqfliteFfiInit();
_secureStorage = const FlutterSecureStorage();
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
- _tempDirectory = _documentsDirectory + "/temp/";
+ _tempDirectory = "$_documentsDirectory/temp/";
final tempDirectory = io.Directory(_tempDirectory);
try {
final currentTime = DateTime.now().microsecondsSinceEpoch;
@@ -162,7 +163,7 @@ class Configuration {
// decrypt the master key
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -172,28 +173,28 @@ class Configuration {
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
// Generate a public-private keypair and encrypt the latter
- final keyPair = await CryptoUtil.generateKeyPair();
+ final keyPair = CryptoUtil.generateKeyPair();
final encryptedSecretKeyData =
- CryptoUtil.encryptSync(keyPair.sk, masterKey);
+ CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
final attributes = KeyAttributes(
- Sodium.bin2base64(kekSalt),
- Sodium.bin2base64(encryptedKeyData.encryptedData!),
- Sodium.bin2base64(encryptedKeyData.nonce!),
- Sodium.bin2base64(keyPair.pk),
- Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
- Sodium.bin2base64(encryptedSecretKeyData.nonce!),
+ CryptoUtil.bin2base64(kekSalt),
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedKeyData.nonce!),
+ CryptoUtil.bin2base64(keyPair.publicKey),
+ CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
derivedKeyResult.memLimit,
derivedKeyResult.opsLimit,
- Sodium.bin2base64(encryptedMasterKey.encryptedData!),
- Sodium.bin2base64(encryptedMasterKey.nonce!),
- Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
- Sodium.bin2base64(encryptedRecoveryKey.nonce!),
+ CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
+ CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
+ CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
+ CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
);
final privateAttributes = PrivateKeyAttributes(
- Sodium.bin2base64(masterKey),
- Sodium.bin2hex(recoveryKey),
- Sodium.bin2base64(keyPair.sk),
+ CryptoUtil.bin2base64(masterKey),
+ CryptoUtil.bin2hex(recoveryKey),
+ CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
);
return KeyGenResult(attributes, privateAttributes, loginKey);
}
@@ -208,7 +209,7 @@ class Configuration {
// decrypt the master key
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -220,9 +221,9 @@ class Configuration {
final existingAttributes = getKeyAttributes();
final updatedAttributes = existingAttributes!.copyWith(
- kekSalt: Sodium.bin2base64(kekSalt),
- encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
- keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
+ kekSalt: CryptoUtil.bin2base64(kekSalt),
+ encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
memLimit: derivedKeyResult.memLimit,
opsLimit: derivedKeyResult.opsLimit,
);
@@ -240,8 +241,8 @@ class Configuration {
}) async {
_logger.info('Start decryptAndSaveSecrets');
keyEncryptionKey ??= await CryptoUtil.deriveKey(
- utf8.encode(password) as Uint8List,
- Sodium.base642bin(attributes.kekSalt),
+ utf8.encode(password),
+ CryptoUtil.base642bin(attributes.kekSalt),
attributes.memLimit,
attributes.opsLimit,
);
@@ -250,31 +251,31 @@ class Configuration {
Uint8List key;
try {
key = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedKey),
+ CryptoUtil.base642bin(attributes.encryptedKey),
keyEncryptionKey,
- Sodium.base642bin(attributes.keyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.keyDecryptionNonce),
);
} catch (e) {
_logger.severe('master-key failed, incorrect password?', e);
throw Exception("Incorrect password");
}
_logger.info("master-key done");
- await setKey(Sodium.bin2base64(key));
+ await setKey(CryptoUtil.bin2base64(key));
final secretKey = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedSecretKey),
+ CryptoUtil.base642bin(attributes.encryptedSecretKey),
key,
- Sodium.base642bin(attributes.secretKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
);
_logger.info("secret-key done");
- await setSecretKey(Sodium.bin2base64(secretKey));
+ await setSecretKey(CryptoUtil.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
- Sodium.base642bin(getEncryptedToken()!),
- Sodium.base642bin(attributes.publicKey),
+ CryptoUtil.base642bin(getEncryptedToken()!),
+ CryptoUtil.base642bin(attributes.publicKey),
secretKey,
);
_logger.info('appToken done');
await setToken(
- Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
+ CryptoUtil.bin2base64(token, urlSafe: true),
);
return keyEncryptionKey;
}
@@ -293,28 +294,28 @@ class Configuration {
Uint8List masterKey;
try {
masterKey = await CryptoUtil.decrypt(
- Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
- Sodium.hex2bin(recoveryKey),
- Sodium.base642bin(attributes.masterKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
+ CryptoUtil.hex2bin(recoveryKey),
+ CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
);
} catch (e) {
_logger.severe(e);
rethrow;
}
- await setKey(Sodium.bin2base64(masterKey));
+ await setKey(CryptoUtil.bin2base64(masterKey));
final secretKey = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedSecretKey),
+ CryptoUtil.base642bin(attributes.encryptedSecretKey),
masterKey,
- Sodium.base642bin(attributes.secretKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
);
- await setSecretKey(Sodium.bin2base64(secretKey));
+ await setSecretKey(CryptoUtil.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
- Sodium.base642bin(getEncryptedToken()!),
- Sodium.base642bin(attributes.publicKey),
+ CryptoUtil.base642bin(getEncryptedToken()!),
+ CryptoUtil.base642bin(attributes.publicKey),
secretKey,
);
await setToken(
- Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
+ CryptoUtil.bin2base64(token, urlSafe: true),
);
}
@@ -407,27 +408,31 @@ class Configuration {
}
Uint8List? getKey() {
- return _key == null ? null : Sodium.base642bin(_key!);
+ return _key == null ? null : CryptoUtil.base642bin(_key!);
}
Uint8List? getSecretKey() {
- return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
+ return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
}
Uint8List? getAuthSecretKey() {
- return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
+ return _authSecretKey == null
+ ? null
+ : CryptoUtil.base642bin(_authSecretKey!);
}
Uint8List? getOfflineSecretKey() {
- return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
+ return _offlineAuthKey == null
+ ? null
+ : CryptoUtil.base642bin(_offlineAuthKey!);
}
Uint8List getRecoveryKey() {
final keyAttributes = getKeyAttributes()!;
return CryptoUtil.decryptSync(
- Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
+ CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
getKey()!,
- Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
+ CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
);
}
@@ -454,7 +459,7 @@ class Configuration {
iOptions: _secureStorageOptionsIOS,
);
} else {
- _offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
+ _offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
await _secureStorage.write(
key: offlineAuthSecretKey,
value: _offlineAuthKey,
diff --git a/auth/lib/core/constants.dart b/auth/lib/core/constants.dart
index dcc111c4b..5685220ac 100644
--- a/auth/lib/core/constants.dart
+++ b/auth/lib/core/constants.dart
@@ -7,7 +7,8 @@ const String sentryDSN =
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
const String sentryTunnel = "https://sentry-reporter.ente.io";
const String roadmapURL = "https://roadmap.ente.io";
-const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions";
+const String githubIssuesUrl =
+ "https://github.com/ente-io/ente/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc";
const int microSecondsInDay = 86400000000;
const int android11SDKINT = 30;
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
diff --git a/auth/lib/core/errors.dart b/auth/lib/core/errors.dart
index c1e83cde0..ba1310b6c 100644
--- a/auth/lib/core/errors.dart
+++ b/auth/lib/core/errors.dart
@@ -1,9 +1,9 @@
class InvalidFileError extends ArgumentError {
- InvalidFileError(String message) : super(message);
+ InvalidFileError(String super.message);
}
class InvalidFileUploadState extends AssertionError {
- InvalidFileUploadState(String message) : super(message);
+ InvalidFileUploadState(String super.message);
}
class SubscriptionAlreadyClaimedError extends Error {}
@@ -30,19 +30,15 @@ class UnauthorizedError extends Error {}
class RequestCancelledError extends Error {}
class InvalidSyncStatusError extends AssertionError {
- InvalidSyncStatusError(String message) : super(message);
+ InvalidSyncStatusError(String super.message);
}
class UnauthorizedEditError extends AssertionError {}
class InvalidStateError extends AssertionError {
- InvalidStateError(String message) : super(message);
+ InvalidStateError(String super.message);
}
-class KeyDerivationError extends Error {}
-
-class LoginKeyDerivationError extends Error {}
-
class SrpSetupNotCompleteError extends Error {}
class AuthenticatorKeyNotFound extends Error {}
diff --git a/auth/lib/core/logging/super_logging.dart b/auth/lib/core/logging/super_logging.dart
index 39a983874..08c3f475e 100644
--- a/auth/lib/core/logging/super_logging.dart
+++ b/auth/lib/core/logging/super_logging.dart
@@ -235,14 +235,14 @@ class SuperLogging {
extraLines = null;
}
- final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
+ final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
// write to stdout
printLog(str);
// push to log queue
if (fileIsEnabled) {
- fileQueueEntries.add(str + '\n');
+ fileQueueEntries.add('$str\n');
if (fileQueueEntries.length == 1) {
flushQueue();
}
@@ -275,7 +275,7 @@ class SuperLogging {
static var logChunkSize = 800;
static void printLog(String text) {
- text.chunked(logChunkSize).forEach(print);
+ text.chunked(logChunkSize).forEach(debugPrint);
}
/// A queue to be consumed by [setupSentry].
@@ -354,7 +354,7 @@ class SuperLogging {
final date = config.dateFmt!.parse(basename(file.path));
dates[file as File] = date;
files.add(file);
- } on FormatException {}
+ } on Exception catch (_) {}
}
final nowTime = DateTime.now();
@@ -374,7 +374,7 @@ class SuperLogging {
"deleting log file ${file.path}",
);
await file.delete();
- } catch (ignore) {}
+ } on Exception catch (_) {}
}
}
diff --git a/auth/lib/core/logging/tunneled_transport.dart b/auth/lib/core/logging/tunneled_transport.dart
index 1191adc2e..f9ffd5ad1 100644
--- a/auth/lib/core/logging/tunneled_transport.dart
+++ b/auth/lib/core/logging/tunneled_transport.dart
@@ -46,7 +46,7 @@ class TunneledTransport implements Transport {
_options.logger(
SentryLevel.error,
'API returned an error, statusCode = ${response.statusCode}, '
- 'body = ${response.body}',
+ 'body = ${response.body}',
);
}
return const SentryId.empty();
@@ -65,8 +65,8 @@ class TunneledTransport implements Transport {
}
Future _createStreamedRequest(
- SentryEnvelope envelope,
- ) async {
+ SentryEnvelope envelope,
+ ) async {
final streamedRequest = StreamedRequest('POST', _tunnel);
envelope
.envelopeStream(_options)
@@ -91,10 +91,10 @@ class _CredentialBuilder {
_clock = clock;
factory _CredentialBuilder(
- Dsn? dsn,
- String sdkIdentifier,
- ClockProvider clock,
- ) {
+ Dsn? dsn,
+ String sdkIdentifier,
+ ClockProvider clock,
+ ) {
final authHeader = _buildAuthHeader(
publicKey: dsn?.publicKey,
secretKey: dsn?.secretKey,
diff --git a/auth/lib/core/network.dart b/auth/lib/core/network.dart
index 1942fcbb8..c14c9e758 100644
--- a/auth/lib/core/network.dart
+++ b/auth/lib/core/network.dart
@@ -4,9 +4,10 @@ import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/endpoint_updated_event.dart';
+import 'package:ente_auth/utils/package_info_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/foundation.dart';
-import 'package:package_info_plus/package_info_plus.dart';
import 'package:uuid/uuid.dart';
int kConnectTimeout = 15000;
@@ -16,34 +17,41 @@ class Network {
late Dio _enteDio;
Future init() async {
- await FkUserAgent.init();
- final packageInfo = await PackageInfo.fromPlatform();
+ if (PlatformUtil.isMobile()) await FkUserAgent.init();
+ final packageInfo = await PackageInfoUtil().getPackageInfo();
+ final version = PackageInfoUtil().getVersion(packageInfo);
+ final packageName = PackageInfoUtil().getPackageName(packageInfo);
final endpoint = Configuration.instance.getHttpEndpoint();
-
+
_dio = Dio(
BaseOptions(
- connectTimeout: kConnectTimeout,
+ connectTimeout: Duration(milliseconds: kConnectTimeout),
headers: {
- HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
- 'X-Client-Version': packageInfo.version,
- 'X-Client-Package': packageInfo.packageName,
+ HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
+ ? FkUserAgent.userAgent
+ : Platform.operatingSystem,
+ 'X-Client-Version': version,
+ 'X-Client-Package': packageName,
},
),
);
-
+
_enteDio = Dio(
BaseOptions(
baseUrl: endpoint,
- connectTimeout: kConnectTimeout,
+ connectTimeout: Duration(milliseconds: kConnectTimeout),
headers: {
- HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
- 'X-Client-Version': packageInfo.version,
- 'X-Client-Package': packageInfo.packageName,
+ if (PlatformUtil.isMobile())
+ HttpHeaders.userAgentHeader: FkUserAgent.userAgent
+ else
+ HttpHeaders.userAgentHeader: Platform.operatingSystem,
+ 'X-Client-Version': version,
+ 'X-Client-Package': packageName,
},
),
);
_setupInterceptors(endpoint);
-
+
Bus.instance.on().listen((event) {
final endpoint = Configuration.instance.getHttpEndpoint();
_enteDio.options.baseUrl = endpoint;
diff --git a/auth/lib/ente_theme_data.dart b/auth/lib/ente_theme_data.dart
index bb2a994c4..0316d014f 100644
--- a/auth/lib/ente_theme_data.dart
+++ b/auth/lib/ente_theme_data.dart
@@ -5,12 +5,16 @@ import 'package:flutter/material.dart';
final lightThemeData = ThemeData(
fontFamily: 'Inter',
brightness: Brightness.light,
+ dividerTheme: const DividerThemeData(
+ color: Colors.black12,
+ ),
hintColor: const Color.fromRGBO(158, 158, 158, 1),
primaryColor: const Color.fromRGBO(255, 110, 64, 1),
primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541),
iconTheme: const IconThemeData(color: Colors.black),
primaryIconTheme:
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
+ buttonTheme: const ButtonThemeData(),
outlinedButtonTheme: buildOutlinedButtonThemeData(
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
@@ -72,24 +76,42 @@ final lightThemeData = ThemeData(
? const Color.fromRGBO(255, 255, 255, 1)
: const Color.fromRGBO(0, 0, 0, 1);
}),
- ), radioTheme: RadioThemeData(
- fillColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), switchTheme: SwitchThemeData(
- thumbColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- trackColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), colorScheme: const ColorScheme.light(
+ ),
+ radioTheme: RadioThemeData(
+ fillColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ trackColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ colorScheme: const ColorScheme.light(
primary: Colors.black,
secondary: Color.fromARGB(255, 163, 163, 163),
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
@@ -98,6 +120,9 @@ final lightThemeData = ThemeData(
final darkThemeData = ThemeData(
fontFamily: 'Inter',
brightness: Brightness.dark,
+ dividerTheme: const DividerThemeData(
+ color: Colors.white12,
+ ),
primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702),
iconTheme: const IconThemeData(color: Colors.white),
primaryIconTheme:
@@ -105,6 +130,7 @@ final darkThemeData = ThemeData(
hintColor: const Color.fromRGBO(158, 158, 158, 1),
buttonTheme: const ButtonThemeData().copyWith(
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
+ height: 56,
),
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
outlinedButtonTheme: buildOutlinedButtonThemeData(
@@ -164,24 +190,43 @@ final darkThemeData = ThemeData(
return const Color.fromRGBO(158, 158, 158, 1);
}
}),
- ), radioTheme: RadioThemeData(
- fillColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), switchTheme: SwitchThemeData(
- thumbColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- trackColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
+ ),
+ radioTheme: RadioThemeData(
+ fillColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ trackColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ colorScheme: const ColorScheme.dark(primary: Colors.white)
+ .copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
);
TextTheme _buildTextTheme(Color textColor) {
@@ -400,6 +445,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
+ fixedSize: const Size.fromHeight(56),
alignment: Alignment.center,
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
textStyle: const TextStyle(
@@ -436,7 +482,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
}) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
- foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
+ foregroundColor: onPrimary,
+ backgroundColor: primary,
+ elevation: elevation,
alignment: Alignment.center,
textStyle: const TextStyle(
fontWeight: FontWeight.w600,
diff --git a/auth/lib/gateway/authenticator.dart b/auth/lib/gateway/authenticator.dart
index ee19c79b0..3f3c7c897 100644
--- a/auth/lib/gateway/authenticator.dart
+++ b/auth/lib/gateway/authenticator.dart
@@ -25,7 +25,7 @@ class AuthenticatorGateway {
try {
final response = await _enteDio.get("/authenticator/key");
return AuthKey.fromMap(response.data);
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
throw AuthenticatorKeyNotFound();
} else {
@@ -90,7 +90,7 @@ class AuthenticatorGateway {
}
return authEntities;
} catch (e) {
- if (e is DioError && e.response?.statusCode == 401) {
+ if (e is DioException && e.response?.statusCode == 401) {
throw UnauthorizedError();
} else {
rethrow;
diff --git a/auth/lib/l10n/arb/app_de.arb b/auth/lib/l10n/arb/app_de.arb
index 32507899c..7b21fcab1 100644
--- a/auth/lib/l10n/arb/app_de.arb
+++ b/auth/lib/l10n/arb/app_de.arb
@@ -145,6 +145,7 @@
"lostDeviceTitle": "Gerät verloren?",
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
"passkeyAuthTitle": "Passkey Authentifizierung",
+ "verifyPasskey": "Passkey verifizieren",
"recoverAccount": "Konto wiederherstellen",
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
"recover": "Wiederherstellen",
@@ -407,6 +408,7 @@
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
"hearUsExplanation": "Wir tracken keine App-Installationen. Es würde uns jedoch helfen, wenn du uns mitteilst, wie du von uns erfahren hast!",
"waitingForBrowserRequest": "Warten auf Browseranfrage...",
+ "waitingForVerification": "Warte auf Bestätigung...",
"passkey": "Passkey",
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
"developerSettings": "Entwicklereinstellungen",
diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb
index f540fc717..a2f6e77ed 100644
--- a/auth/lib/l10n/arb/app_en.arb
+++ b/auth/lib/l10n/arb/app_en.arb
@@ -199,6 +199,10 @@
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
"doThisLater": "Do this later",
"saveKey": "Save key",
+ "save": "Save",
+ "send": "Send",
+ "saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?",
+ "saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?",
"back": "Back",
"createAccount": "Create account",
"passwordStrength": "Password strength: {passwordStrengthValue}",
@@ -407,6 +411,7 @@
"doNotSignOut": "Do not sign out",
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
+ "recoveryKeySaved": "Recovery key saved in Downloads folder!",
"waitingForBrowserRequest": "Waiting for browser request...",
"waitingForVerification": "Waiting for verification...",
"passkey": "Passkey",
diff --git a/auth/lib/l10n/arb/app_ko.arb b/auth/lib/l10n/arb/app_ko.arb
new file mode 100644
index 000000000..9e26dfeeb
--- /dev/null
+++ b/auth/lib/l10n/arb/app_ko.arb
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/auth/lib/l10n/arb/app_nl.arb b/auth/lib/l10n/arb/app_nl.arb
index 0ac2b3791..ad1728204 100644
--- a/auth/lib/l10n/arb/app_nl.arb
+++ b/auth/lib/l10n/arb/app_nl.arb
@@ -144,6 +144,8 @@
"enterCodeHint": "Voer de 6-cijferige code van je verificatie-app in",
"lostDeviceTitle": "Apparaat verloren?",
"twoFactorAuthTitle": "Tweestapsverificatie",
+ "passkeyAuthTitle": "Passkey verificatie",
+ "verifyPasskey": "Bevestig passkey",
"recoverAccount": "Account herstellen",
"enterRecoveryKeyHint": "Voer je herstelsleutel in",
"recover": "Herstellen",
@@ -404,5 +406,15 @@
"signOutOtherDevices": "Afmelden bij andere apparaten",
"doNotSignOut": "Niet uitloggen",
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
- "hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!"
+ "hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!",
+ "waitingForBrowserRequest": "Wachten op browserverzoek...",
+ "waitingForVerification": "Wachten op verificatie...",
+ "passkey": "Passkey",
+ "developerSettingsWarning": "Weet u zeker dat u de ontwikkelaarsinstellingen wilt wijzigen?",
+ "developerSettings": "Ontwikkelaarsinstellingen",
+ "serverEndpoint": "Server eindpunt",
+ "invalidEndpoint": "Ongeldig eindpunt",
+ "invalidEndpointMessage": "Sorry, het eindpunt dat u hebt ingevoerd is ongeldig. Voer een geldig eindpunt in en probeer het opnieuw.",
+ "endpointUpdatedMessage": "Eindpunt met succes bijgewerkt",
+ "customEndpoint": "Verbonden met {endpoint}"
}
\ No newline at end of file
diff --git a/auth/lib/l10n/arb/app_pl.arb b/auth/lib/l10n/arb/app_pl.arb
index f36c690f7..3221799d0 100644
--- a/auth/lib/l10n/arb/app_pl.arb
+++ b/auth/lib/l10n/arb/app_pl.arb
@@ -338,5 +338,22 @@
"deleteCodeAuthMessage": "Uwierzytelnij, aby usunąć kod",
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR",
"confirmAccountDeleteTitle": "Potwierdź usunięcie konta",
- "confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte."
+ "confirmAccountDeleteMessage": "To konto jest połączone z innymi aplikacjami ente, jeśli ich używasz.\n\nTwoje przesłane dane, we wszystkich aplikacjach ente, zostaną zaplanowane do usunięcia, a Twoje konto zostanie trwale usunięte.",
+ "androidBiometricNotRecognized": "Nie rozpoznano. Spróbuj ponownie.",
+ "@androidBiometricNotRecognized": {
+ "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
+ },
+ "androidSignInTitle": "Wymagana autoryzacja",
+ "@androidSignInTitle": {
+ "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
+ },
+ "goToSettings": "Przejdź do Ustawień",
+ "@goToSettings": {
+ "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
+ },
+ "noInternetConnection": "Brak połączenia z Internetem",
+ "pleaseCheckYourInternetConnectionAndTryAgain": "Proszę sprawdzić połączenie internetowe i spróbować ponownie.",
+ "hearUsWhereTitle": "Jak usłyszałeś o Ente? (opcjonalnie)",
+ "waitingForVerification": "Oczekiwanie na weryfikację...",
+ "developerSettings": "Ustawienia deweloperskie"
}
\ No newline at end of file
diff --git a/auth/lib/l10n/arb/app_pt.arb b/auth/lib/l10n/arb/app_pt.arb
index aa6a2a837..e6ca32893 100644
--- a/auth/lib/l10n/arb/app_pt.arb
+++ b/auth/lib/l10n/arb/app_pt.arb
@@ -47,9 +47,9 @@
},
"copyEmailAction": "Copiar e-mail",
"exportLogsAction": "Exportar logs",
- "reportABug": "Reportar um bug",
+ "reportABug": "Reportar um problema",
"crashAndErrorReporting": "Reporte de erros e falhas",
- "reportBug": "Reportar bug",
+ "reportBug": "Reportar problema",
"emailUsMessage": "Por favor, envie um e-mail para {email}",
"@emailUsMessage": {
"placeholders": {
@@ -145,6 +145,7 @@
"lostDeviceTitle": "Perdeu seu dispositivo?",
"twoFactorAuthTitle": "Autenticação de dois fatores",
"passkeyAuthTitle": "Autenticação via Chave de acesso",
+ "verifyPasskey": "Verificar chave de acesso",
"recoverAccount": "Recuperar conta",
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
"recover": "Recuperar",
@@ -162,7 +163,7 @@
"invalidEmailMessage": "Por favor, insira um endereço de e-mail válido.",
"deleteAccount": "Excluir conta",
"deleteAccountQuery": "Sentiremos muito por vê-lo partir. Você está enfrentando algum problema?",
- "yesSendFeedbackAction": "Sim, enviar feedback",
+ "yesSendFeedbackAction": "Sim, enviar comentário",
"noDeleteAccountAction": "Não, excluir conta",
"initiateAccountDeleteTitle": "Por favor, autentique-se para iniciar a exclusão de conta",
"sendEmail": "Enviar e-mail",
@@ -407,6 +408,7 @@
"hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
+ "waitingForVerification": "Esperando por verificação...",
"passkey": "Chave de acesso",
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
"developerSettings": "Configurações de desenvolvedor",
diff --git a/auth/lib/l10n/arb/app_zh.arb b/auth/lib/l10n/arb/app_zh.arb
index e07e7dcfa..5328a14b9 100644
--- a/auth/lib/l10n/arb/app_zh.arb
+++ b/auth/lib/l10n/arb/app_zh.arb
@@ -145,6 +145,7 @@
"lostDeviceTitle": "丢失了设备吗?",
"twoFactorAuthTitle": "双因素认证",
"passkeyAuthTitle": "通行密钥认证",
+ "verifyPasskey": "验证通行密钥",
"recoverAccount": "恢复账户",
"enterRecoveryKeyHint": "输入您的恢复密钥",
"recover": "恢复",
@@ -407,6 +408,7 @@
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
"waitingForBrowserRequest": "正在等待浏览器请求...",
+ "waitingForVerification": "等待验证...",
"passkey": "通行密钥",
"developerSettingsWarning": "您确定要修改开发者设置吗?",
"developerSettings": "开发者设置",
diff --git a/auth/lib/main.dart b/auth/lib/main.dart
index 02d3e0c12..09b85d8b3 100644
--- a/auth/lib/main.dart
+++ b/auth/lib/main.dart
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io';
import 'package:adaptive_theme/adaptive_theme.dart';
-import 'package:computer/computer.dart';
import "package:ente_auth/app/view/app.dart";
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
@@ -17,11 +16,14 @@ import 'package:ente_auth/services/preference_service.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/services/user_remote_flag_service.dart';
import 'package:ente_auth/services/user_service.dart';
+import 'package:ente_auth/services/window_listener_service.dart';
import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/ui/tools/lock_screen.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/window_protocol_handler.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/foundation.dart';
import "package:flutter/material.dart";
import 'package:flutter/scheduler.dart';
@@ -29,11 +31,52 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:privacy_screen/privacy_screen.dart';
+import 'package:tray_manager/tray_manager.dart';
+import 'package:window_manager/window_manager.dart';
final _logger = Logger("main");
+Future initSystemTray() async {
+ String path = Platform.isWindows
+ ? 'assets/icons/auth-icon.ico'
+ : 'assets/icons/auth-icon.png';
+ await trayManager.setIcon(path);
+ Menu menu = Menu(
+ items: [
+ MenuItem(
+ key: 'hide_window',
+ label: 'Hide Window',
+ ),
+ MenuItem(
+ key: 'show_window',
+ label: 'Show Window',
+ ),
+ MenuItem.separator(),
+ MenuItem(
+ key: 'exit_app',
+ label: 'Exit App',
+ ),
+ ],
+ );
+ await trayManager.setContextMenu(menu);
+}
+
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+
+ initSystemTray().ignore();
+
+ if (PlatformUtil.isDesktop()) {
+ await windowManager.ensureInitialized();
+ await WindowListenerService.instance.init();
+ WindowOptions windowOptions = WindowOptions(
+ size: WindowListenerService.instance.getWindowSize(),
+ );
+ await windowManager.waitUntilReadyToShow(windowOptions, () async {
+ await windowManager.show();
+ await windowManager.focus();
+ });
+ }
await _runInForeground();
await _setupPrivacyScreen();
if (Platform.isAndroid) {
@@ -70,10 +113,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
}
Future _runWithLogs(Function() function, {String prefix = ""}) async {
+ String dir = "";
+ try {
+ dir = "${(await getApplicationSupportDirectory()).path}/logs";
+ } catch (_) {}
await SuperLogging.main(
LogConfig(
body: function,
- logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
+ logDirPath: dir,
maxLogFiles: 5,
sentryDsn: sentryDSN,
enableInDebugMode: true,
@@ -82,10 +129,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
);
}
+void _registerWindowsProtocol() {
+ const kWindowsScheme = 'ente';
+ // Register our protocol only on Windows platform
+ if (!kIsWeb && Platform.isWindows) {
+ WindowsProtocolHandler()
+ .register(kWindowsScheme, executable: null, arguments: null);
+ }
+}
+
Future _init(bool bool, {String? via}) async {
- // Start workers asynchronously. No need to wait for them to start
- Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
- CryptoUtil.init();
+ _registerWindowsProtocol();
+ await initCryptoUtil();
+
await PreferenceService.instance.init();
await CodeStore.instance.init();
await Configuration.instance.init();
@@ -100,6 +156,7 @@ Future _init(bool bool, {String? via}) async {
}
Future _setupPrivacyScreen() async {
+ if (!PlatformUtil.isMobile()) return;
final brightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness;
bool isInDarkMode = brightness == Brightness.dark;
diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart
index 40c8c6455..7853eb19d 100644
--- a/auth/lib/models/code.dart
+++ b/auth/lib/models/code.dart
@@ -57,14 +57,7 @@ class Code {
updatedAlgo,
updatedType,
updatedCounter,
- "otpauth://${updatedType.name}/" +
- updateIssuer +
- ":" +
- updateAccount +
- "?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
- updateIssuer +
- "&period=$updatePeriod&secret=" +
- updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
+ "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
generatedID: generatedID,
);
}
@@ -83,35 +76,28 @@ class Code {
Algorithm.sha1,
Type.totp,
0,
- "otpauth://totp/" +
- issuer +
- ":" +
- account +
- "?algorithm=SHA1&digits=6&issuer=" +
- issuer +
- "&period=30&secret=" +
- secret,
+ "otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
);
}
static Code fromRawData(String rawData) {
Uri uri = Uri.parse(rawData);
try {
- return Code(
- _getAccount(uri),
- _getIssuer(uri),
- _getDigits(uri),
- _getPeriod(uri),
- getSanitizedSecret(uri.queryParameters['secret']!),
- _getAlgorithm(uri),
- _getType(uri),
- _getCounter(uri),
- rawData,
- );
- } catch(e) {
+ return Code(
+ _getAccount(uri),
+ _getIssuer(uri),
+ _getDigits(uri),
+ _getPeriod(uri),
+ getSanitizedSecret(uri.queryParameters['secret']!),
+ _getAlgorithm(uri),
+ _getType(uri),
+ _getCounter(uri),
+ rawData,
+ );
+ } catch (e) {
// if account name contains # without encoding,
// rest of the url are treated as url fragment
- if(rawData.contains("#")) {
+ if (rawData.contains("#")) {
return Code.fromRawData(rawData.replaceAll("#", '%23'));
} else {
rethrow;
@@ -141,7 +127,7 @@ class Code {
if (uri.queryParameters.containsKey("issuer")) {
String issuerName = uri.queryParameters['issuer']!;
// Handle issuer name with period
- // See https://github.com/ente-io/auth/pull/77
+ // See https://github.com/ente-io/ente/pull/77
if (issuerName.contains("period=")) {
return issuerName.substring(0, issuerName.indexOf("period="));
}
diff --git a/auth/lib/models/derived_key_result.dart b/auth/lib/models/derived_key_result.dart
deleted file mode 100644
index a071fb1f8..000000000
--- a/auth/lib/models/derived_key_result.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'dart:typed_data';
-
-class DerivedKeyResult {
- final Uint8List key;
- final int memLimit;
- final int opsLimit;
-
- DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
-}
diff --git a/auth/lib/models/encryption_result.dart b/auth/lib/models/encryption_result.dart
deleted file mode 100644
index 9da16c573..000000000
--- a/auth/lib/models/encryption_result.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'dart:typed_data';
-
-class EncryptionResult {
- final Uint8List? encryptedData;
- final Uint8List? key;
- final Uint8List? header;
- final Uint8List? nonce;
-
- EncryptionResult({
- this.encryptedData,
- this.key,
- this.header,
- this.nonce,
- });
-}
diff --git a/auth/lib/onboarding/view/onboarding_page.dart b/auth/lib/onboarding/view/onboarding_page.dart
index b97c3436a..a0bc6fb66 100644
--- a/auth/lib/onboarding/view/onboarding_page.dart
+++ b/auth/lib/onboarding/view/onboarding_page.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:io';
import 'package:ente_auth/app/view/app.dart';
import 'package:ente_auth/core/configuration.dart';
@@ -28,7 +29,7 @@ import "package:flutter/material.dart";
import 'package:local_auth/local_auth.dart';
class OnboardingPage extends StatefulWidget {
- const OnboardingPage({Key? key}) : super(key: key);
+ const OnboardingPage({super.key});
@override
State createState() => _OnboardingPageState();
@@ -86,118 +87,128 @@ class _OnboardingPageState extends State {
}
}
},
- child: Center(
- child: SingleChildScrollView(
- child: Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
- child: Column(
- children: [
- Column(
- children: [
- kDebugMode
- ? GestureDetector(
- child: const Align(
- alignment: Alignment.topRight,
- child: Text("Lang"),
- ),
- onTap: () async {
- final locale = await getLocale();
- // ignore: unawaited_futures
- routeToPage(
- context,
- LanguageSelectorPage(
- appSupportedLocales,
- (locale) async {
- await setLocale(locale);
- App.setLocale(context, locale);
- },
- locale,
- ),
- ).then((value) {
- setState(() {});
- });
- },
- )
- : const SizedBox(),
- Image.asset(
- "assets/sheild-front-gradient.png",
- width: 200,
- height: 200,
- ),
- const SizedBox(height: 12),
- const Text(
- "ente",
- style: TextStyle(
- fontWeight: FontWeight.bold,
- fontFamily: 'Montserrat',
- fontSize: 42,
- ),
- ),
- const SizedBox(height: 4),
- Text(
- "Authenticator",
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- const SizedBox(height: 32),
- Text(
- l10n.onBoardingBody,
- textAlign: TextAlign.center,
- style:
- Theme.of(context).textTheme.titleLarge!.copyWith(
- color: Colors.white38,
+ child: SingleChildScrollView(
+ child: Center(
+ child: ConstrainedBox(
+ constraints:
+ const BoxConstraints.tightFor(height: 800, width: 450),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 40.0,
+ horizontal: 40,
+ ),
+ child: Column(
+ children: [
+ Column(
+ children: [
+ kDebugMode
+ ? GestureDetector(
+ child: const Align(
+ alignment: Alignment.topRight,
+ child: Text("Lang"),
),
- ),
- ],
- ),
- const SizedBox(height: 100),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.symmetric(horizontal: 20),
- child: GradientButton(
- onTap: _navigateToSignUpPage,
- text: l10n.newUser,
+ onTap: () async {
+ final locale = await getLocale();
+ // ignore: unawaited_futures
+ routeToPage(
+ context,
+ LanguageSelectorPage(
+ appSupportedLocales,
+ (locale) async {
+ await setLocale(locale);
+ App.setLocale(context, locale);
+ },
+ locale,
+ ),
+ ).then((value) {
+ setState(() {});
+ });
+ },
+ )
+ : const SizedBox(),
+ Image.asset(
+ "assets/sheild-front-gradient.png",
+ width: 200,
+ height: 200,
+ ),
+ const SizedBox(height: 12),
+ const Text(
+ "ente",
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontFamily: 'Montserrat',
+ fontSize: 42,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ "Authenticator",
+ style: Theme.of(context).textTheme.headlineMedium,
+ ),
+ const SizedBox(height: 32),
+ Text(
+ l10n.onBoardingBody,
+ textAlign: TextAlign.center,
+ style: Theme.of(context)
+ .textTheme
+ .titleLarge!
+ .copyWith(
+ color: Colors.white38,
+ ),
+ ),
+ ],
),
- ),
- const SizedBox(height: 4),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
- child: Hero(
- tag: "log_in",
- child: ElevatedButton(
- style: Theme.of(context)
- .colorScheme
- .optionalActionButtonStyle,
- onPressed: _navigateToSignInPage,
- child: Text(
- l10n.existingUser,
- style: const TextStyle(
- color: Colors.black, // same for both themes
+ const SizedBox(height: 100),
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: GradientButton(
+ onTap: _navigateToSignUpPage,
+ text: l10n.newUser,
+ ),
+ ),
+ const SizedBox(height: 16),
+ Container(
+ height: 56,
+ width: double.infinity,
+ padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
+ child: Hero(
+ tag: "log_in",
+ child: ElevatedButton(
+ style: Theme.of(context)
+ .colorScheme
+ .optionalActionButtonStyle,
+ onPressed: _navigateToSignInPage,
+ child: Text(
+ l10n.existingUser,
+ style: const TextStyle(
+ color: Colors.black, // same for both themes
+ ),
),
),
),
),
- ),
- const SizedBox(height: 4),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.only(top: 20, bottom: 20),
- child: GestureDetector(
- onTap: _optForOfflineMode,
- child: Center(
- child: Text(
- l10n.useOffline,
- style: body.copyWith(
- color:
- Theme.of(context).colorScheme.mutedTextColor,
+ const SizedBox(height: 4),
+ Container(
+ width: double.infinity,
+ padding: const EdgeInsets.only(top: 20, bottom: 20),
+ child: GestureDetector(
+ onTap: _optForOfflineMode,
+ child: Center(
+ child: Text(
+ l10n.useOffline,
+ style: body.copyWith(
+ color: Theme.of(context)
+ .colorScheme
+ .mutedTextColor,
+ ),
),
),
),
),
- ),
- const DeveloperSettingsWidget(),
- ],
+ const DeveloperSettingsWidget(),
+ ],
+ ),
),
),
),
@@ -208,7 +219,9 @@ class _OnboardingPageState extends State {
}
Future _optForOfflineMode() async {
- bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
+ bool canCheckBio = Platform.isMacOS || Platform.isLinux
+ ? true
+ : await LocalAuthentication().canCheckBiometrics;
if (!canCheckBio) {
showToast(
context,
diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
index ee46d7953..3937142d6 100644
--- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
+++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
@@ -9,7 +9,7 @@ import "package:flutter/material.dart";
class SetupEnterSecretKeyPage extends StatefulWidget {
final Code? code;
- SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
+ SetupEnterSecretKeyPage({this.code, super.key});
@override
State createState() =>
@@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State {
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
);
_secretController = TextEditingController(
- text: widget.code != null ? widget.code!.secret : null,
+ text: widget.code?.secret,
);
_secretKeyObscured = widget.code != null;
super.initState();
@@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State {
appBar: AppBar(
title: Text(l10n.importAccountPageTitle),
),
- body: SafeArea(
- child: Center(
+ body: Center(
+ child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
diff --git a/auth/lib/onboarding/view/view_qr_page.dart b/auth/lib/onboarding/view/view_qr_page.dart
index 71f4756a7..687f810e3 100644
--- a/auth/lib/onboarding/view/view_qr_page.dart
+++ b/auth/lib/onboarding/view/view_qr_page.dart
@@ -1,4 +1,3 @@
-
import 'dart:math';
import "package:ente_auth/l10n/l10n.dart";
@@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
class ViewQrPage extends StatelessWidget {
final Code? code;
- ViewQrPage({this.code, Key? key}) : super(key: key);
+ ViewQrPage({this.code, super.key});
@override
Widget build(BuildContext context) {
@@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
appBar: AppBar(
title: Text(l10n.qrCode),
),
- body: SafeArea(
- child: Center(
+ body: Center(
+ child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
children: [
- QrImage(
+ QrImageView(
data: code!.rawData,
- foregroundColor: Theme.of(context).colorScheme.onBackground,
+ eyeStyle: QrEyeStyle(
+ eyeShape: QrEyeShape.square,
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ dataModuleStyle: QrDataModuleStyle(
+ dataModuleShape: QrDataModuleShape.square,
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
version: QrVersions.auto,
size: qrSize,
),
diff --git a/auth/lib/services/authenticator_service.dart b/auth/lib/services/authenticator_service.dart
index 77d9b0437..ee0ad32bd 100644
--- a/auth/lib/services/authenticator_service.dart
+++ b/auth/lib/services/authenticator_service.dart
@@ -15,9 +15,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
import 'package:ente_auth/store/authenticator_db.dart';
import 'package:ente_auth/store/offline_authenticator_db.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -75,10 +74,10 @@ class AuthenticatorService {
final key = await getOrCreateAuthDataKey(mode);
for (LocalAuthEntity e in result) {
try {
- final decryptedValue = await CryptoUtil.decryptChaCha(
- Sodium.base642bin(e.encryptedData),
+ final decryptedValue = await CryptoUtil.decryptData(
+ CryptoUtil.base642bin(e.encryptedData),
key,
- Sodium.base642bin(e.header),
+ CryptoUtil.base642bin(e.header),
);
final hasSynced = !(e.id == null || e.shouldSync);
entities.add(
@@ -101,12 +100,13 @@ class AuthenticatorService {
AccountMode accountMode,
) async {
var key = await getOrCreateAuthDataKey(accountMode);
- final encryptedKeyData = await CryptoUtil.encryptChaCha(
- utf8.encode(plainText) as Uint8List,
+ final encryptedKeyData = await CryptoUtil.encryptData(
+ utf8.encode(plainText),
key,
);
- String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
- String header = Sodium.bin2base64(encryptedKeyData.header!);
+ String encryptedData =
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
+ String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
final insertedID = accountMode.isOnline
? await _db.insert(encryptedData, header)
: await _offlineDb.insert(encryptedData, header);
@@ -123,12 +123,13 @@ class AuthenticatorService {
AccountMode accountMode,
) async {
var key = await getOrCreateAuthDataKey(accountMode);
- final encryptedKeyData = await CryptoUtil.encryptChaCha(
- utf8.encode(plainText) as Uint8List,
+ final encryptedKeyData = await CryptoUtil.encryptData(
+ utf8.encode(plainText),
key,
);
- String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
- String header = Sodium.bin2base64(encryptedKeyData.header!);
+ String encryptedData =
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
+ String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
final int affectedRows = accountMode.isOnline
? await _db.updateEntry(generatedID, encryptedData, header)
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
@@ -191,25 +192,25 @@ class AuthenticatorService {
Future _remoteToLocalSync() async {
_logger.info('Initiating remote to local sync');
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
- _logger.info("Current sync is " + lastSyncTime.toString());
+ _logger.info("Current sync is $lastSyncTime");
const int fetchLimit = 500;
final List result =
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
- _logger.info(result.length.toString() + " entries fetched from remote");
+ _logger.info("${result.length} entries fetched from remote");
if (result.isEmpty) {
return;
}
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
List deletedIDs =
result.where((element) => element.isDeleted).map((e) => e.id).toList();
- _logger.info(deletedIDs.length.toString() + " entries deleted");
+ _logger.info("${deletedIDs.length} entries deleted");
result.removeWhere((element) => element.isDeleted);
await _db.insertOrReplace(result);
if (deletedIDs.isNotEmpty) {
await _db.deleteByIDs(ids: deletedIDs);
}
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
- _logger.info("Setting synctime to " + maxSyncTime.toString());
+ _logger.info("Setting synctime to $maxSyncTime");
if (result.length == fetchLimit) {
_logger.info("Diff limit reached, pulling again");
await _remoteToLocalSync();
@@ -223,7 +224,7 @@ class AuthenticatorService {
.where((element) => element.shouldSync || element.id == null)
.toList();
_logger.info(
- pendingUpdate.length.toString() + " entries to be updated at remote",
+ "${pendingUpdate.length} entries to be updated at remote",
);
for (LocalAuthEntity entity in pendingUpdate) {
if (entity.id == null) {
@@ -262,21 +263,21 @@ class AuthenticatorService {
try {
final AuthKey response = await _gateway.getKey();
final authKey = CryptoUtil.decryptSync(
- Sodium.base642bin(response.encryptedKey),
+ CryptoUtil.base642bin(response.encryptedKey),
_config.getKey()!,
- Sodium.base642bin(response.header),
+ CryptoUtil.base642bin(response.header),
);
- await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
+ await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
return authKey;
} on AuthenticatorKeyNotFound catch (e) {
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
final key = CryptoUtil.generateKey();
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
await _gateway.createKey(
- Sodium.bin2base64(encryptedKeyData.encryptedData!),
- Sodium.bin2base64(encryptedKeyData.nonce!),
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedKeyData.nonce!),
);
- await _config.setAuthSecretKey(Sodium.bin2base64(key));
+ await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
return key;
} catch (e, s) {
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
diff --git a/auth/lib/services/billing_service.dart b/auth/lib/services/billing_service.dart
index 75a4e5579..363425cd5 100644
--- a/auth/lib/services/billing_service.dart
+++ b/auth/lib/services/billing_service.dart
@@ -50,7 +50,7 @@ class BillingService {
Future> _fetchPrivateBillingPlans() {
return _dio.get(
- _config.getHttpEndpoint() + "/billing/user-plans/",
+ "${_config.getHttpEndpoint()}/billing/user-plans/",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -60,7 +60,7 @@ class BillingService {
}
Future> _fetchPublicBillingPlans() {
- return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
+ return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
}
Future verifySubscription(
@@ -70,7 +70,7 @@ class BillingService {
}) async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/verify-subscription",
+ "${_config.getHttpEndpoint()}/billing/verify-subscription",
data: {
"paymentProvider": paymentProvider ??
(Platform.isAndroid ? "playstore" : "appstore"),
@@ -84,7 +84,7 @@ class BillingService {
),
);
return Subscription.fromMap(response.data["subscription"]);
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && e.response!.statusCode == 409) {
throw SubscriptionAlreadyClaimedError();
} else {
@@ -100,7 +100,7 @@ class BillingService {
if (_cachedSubscription == null) {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/billing/subscription",
+ "${_config.getHttpEndpoint()}/billing/subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -109,7 +109,7 @@ class BillingService {
);
_cachedSubscription =
Subscription.fromMap(response.data["subscription"]);
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -120,7 +120,7 @@ class BillingService {
Future cancelStripeSubscription() async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
+ "${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -129,7 +129,7 @@ class BillingService {
);
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -138,7 +138,7 @@ class BillingService {
Future activateStripeSubscription() async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
+ "${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -147,7 +147,7 @@ class BillingService {
);
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -158,7 +158,7 @@ class BillingService {
}) async {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/billing/stripe/customer-portal",
+ "${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
queryParameters: {
"redirectURL": kWebPaymentRedirectUrl,
},
@@ -169,7 +169,7 @@ class BillingService {
),
);
return response.data["url"];
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
diff --git a/auth/lib/services/local_authentication_service.dart b/auth/lib/services/local_authentication_service.dart
index b7b000dd9..44c2a758a 100644
--- a/auth/lib/services/local_authentication_service.dart
+++ b/auth/lib/services/local_authentication_service.dart
@@ -1,15 +1,21 @@
+import 'dart:io';
+
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_local_authentication/flutter_local_authentication.dart';
import 'package:local_auth/local_auth.dart';
+import 'package:logging/logging.dart';
class LocalAuthenticationService {
LocalAuthenticationService._privateConstructor();
static final LocalAuthenticationService instance =
LocalAuthenticationService._privateConstructor();
+ final logger = Logger((LocalAuthenticationService).toString());
Future requestLocalAuthentication(
BuildContext context,
@@ -38,7 +44,7 @@ class LocalAuthenticationService {
String errorDialogContent, [
String errorDialogTitle = "",
]) async {
- if (await LocalAuthentication().isDeviceSupported()) {
+ if (await _isLocalAuthSupportedOnDevice()) {
AppLock.of(context)!.disable();
final result = await requestAuthentication(
context,
@@ -65,6 +71,12 @@ class LocalAuthenticationService {
}
Future _isLocalAuthSupportedOnDevice() async {
- return await LocalAuthentication().isDeviceSupported();
+ try {
+ return Platform.isMacOS || Platform.isLinux
+ ? await FlutterLocalAuthentication().canAuthenticate()
+ : await LocalAuthentication().isDeviceSupported();
+ } on MissingPluginException {
+ return false;
+ }
}
}
diff --git a/auth/lib/services/notification_service.dart b/auth/lib/services/notification_service.dart
index 60d12811b..63410eb7d 100644
--- a/auth/lib/services/notification_service.dart
+++ b/auth/lib/services/notification_service.dart
@@ -27,8 +27,7 @@ class NotificationService {
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (implementation != null) {
- // ignore: unawaited_futures
- implementation.requestPermission();
+ await implementation.requestNotificationsPermission();
}
}
diff --git a/auth/lib/services/update_service.dart b/auth/lib/services/update_service.dart
index 09f36df83..e102a5a94 100644
--- a/auth/lib/services/update_service.dart
+++ b/auth/lib/services/update_service.dart
@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/core/network.dart';
import 'package:ente_auth/services/notification_service.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:logging/logging.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -130,7 +131,8 @@ class UpdateService {
bool isIndependent() {
return flavor == "independent" ||
- _packageInfo.packageName.endsWith("independent");
+ _packageInfo.packageName.endsWith("independent") ||
+ PlatformUtil.isDesktop();
}
}
@@ -141,6 +143,7 @@ class LatestVersionInfo {
final bool? shouldForceUpdate;
final int lastSupportedVersionCode;
final String? url;
+ final String? release;
final int? size;
final bool? shouldNotify;
@@ -151,6 +154,7 @@ class LatestVersionInfo {
this.shouldForceUpdate,
this.lastSupportedVersionCode,
this.url,
+ this.release,
this.size,
this.shouldNotify,
);
@@ -163,6 +167,7 @@ class LatestVersionInfo {
map['shouldForceUpdate'],
map['lastSupportedVersionCode'] ?? 1,
map['url'],
+ map['release'],
map['size'],
map['shouldNotify'],
);
diff --git a/auth/lib/services/user_remote_flag_service.dart b/auth/lib/services/user_remote_flag_service.dart
index 6961518aa..74deae584 100644
--- a/auth/lib/services/user_remote_flag_service.dart
+++ b/auth/lib/services/user_remote_flag_service.dart
@@ -96,7 +96,7 @@ class UserRemoteFlagService {
queryParams["defaultValue"] = defaultValue;
}
final response = await _dio.get(
- _config.getHttpEndpoint() + "/remote-store",
+ "${_config.getHttpEndpoint()}/remote-store",
queryParameters: queryParams,
options: Options(
headers: {
@@ -119,7 +119,7 @@ class UserRemoteFlagService {
Future _updateKeyValue(String key, String value) async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/remote-store/update",
+ "${_config.getHttpEndpoint()}/remote-store/update",
data: {
"key": key,
"value": value,
diff --git a/auth/lib/services/user_service.dart b/auth/lib/services/user_service.dart
index b870aa647..929468059 100644
--- a/auth/lib/services/user_service.dart
+++ b/auth/lib/services/user_service.dart
@@ -30,9 +30,9 @@ import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/passkey_page.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -80,7 +80,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/ott",
+ "${_config.getHttpEndpoint()}/users/ott",
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
);
await dialog.hide();
@@ -102,7 +102,7 @@ class UserService {
return;
}
unawaited(showGenericErrorDialog(context: context));
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.info(e);
if (e.response != null && e.response!.statusCode == 403) {
@@ -129,7 +129,7 @@ class UserService {
String type = "SubCancellation",
}) async {
await _dio.post(
- _config.getHttpEndpoint() + "/anonymous/feedback",
+ "${_config.getHttpEndpoint()}/anonymous/feedback",
data: {"feedback": feedback, "type": "type"},
);
}
@@ -173,7 +173,7 @@ class UserService {
try {
final response = await _enteDio.get("/users/sessions");
return Sessions.fromMap(response.data);
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
rethrow;
}
@@ -187,7 +187,7 @@ class UserService {
"token": token,
},
);
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
rethrow;
}
@@ -196,7 +196,7 @@ class UserService {
Future leaveFamilyPlan() async {
try {
await _enteDio.delete("/family/leave");
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.warning('failed to leave family plan', e);
rethrow;
}
@@ -306,11 +306,11 @@ class UserService {
"ott": ott,
};
if (!_config.isLoggedIn()) {
- verifyData["source"] = 'auth:' + _getRefSource();
+ verifyData["source"] = 'auth:${_getRefSource()}';
}
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/verify-email",
+ "${_config.getHttpEndpoint()}/users/verify-email",
data: verifyData,
);
await dialog.hide();
@@ -346,7 +346,7 @@ class UserService {
// should never reach here
throw Exception("unexpected response during email verification");
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
await dialog.hide();
if (e.response != null && e.response!.statusCode == 410) {
@@ -410,7 +410,7 @@ class UserService {
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 403) {
// ignore: unawaited_futures
@@ -460,7 +460,7 @@ class UserService {
Future getSrpAttributes(String email) async {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/users/srp/attributes",
+ "${_config.getHttpEndpoint()}/users/srp/attributes",
queryParameters: {
"email": email,
},
@@ -470,7 +470,7 @@ class UserService {
} else {
throw Exception("get-srp-attributes action failed");
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && e.response!.statusCode == 404) {
throw SrpSetupNotCompleteError();
}
@@ -523,7 +523,7 @@ class UserService {
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
- // ignore: unused_local_variable
+
late Response _;
if (setKeysRequest == null) {
_ = await _enteDio.post(
@@ -573,7 +573,7 @@ class UserService {
late Uint8List keyEncryptionKey;
_logger.finest('Start deriving key');
keyEncryptionKey = await CryptoUtil.deriveKey(
- utf8.encode(userPassword) as Uint8List,
+ utf8.encode(userPassword),
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
@@ -596,7 +596,7 @@ class UserService {
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
- _config.getHttpEndpoint() + "/users/srp/create-session",
+ "${_config.getHttpEndpoint()}/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
@@ -610,7 +610,7 @@ class UserService {
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/srp/verify-session",
+ "${_config.getHttpEndpoint()}/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
@@ -709,7 +709,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/two-factor/verify",
+ "${_config.getHttpEndpoint()}/users/two-factor/verify",
data: {
"sessionID": sessionID,
"code": code,
@@ -729,7 +729,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -772,7 +772,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/users/two-factor/recover",
+ "${_config.getHttpEndpoint()}/users/two-factor/recover",
queryParameters: {
"sessionID": sessionID,
"twoFactorType": twoFactorTypeToString(type),
@@ -794,7 +794,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -868,7 +868,7 @@ class UserService {
}
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/two-factor/remove",
+ "${_config.getHttpEndpoint()}/users/two-factor/remove",
data: {
"sessionID": sessionID,
"secret": secret,
@@ -891,7 +891,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
diff --git a/auth/lib/services/window_listener_service.dart b/auth/lib/services/window_listener_service.dart
new file mode 100644
index 000000000..9ceefef9a
--- /dev/null
+++ b/auth/lib/services/window_listener_service.dart
@@ -0,0 +1,36 @@
+import 'dart:async';
+import 'dart:ui';
+
+import 'package:shared_preferences/shared_preferences.dart';
+import 'package:window_manager/window_manager.dart';
+
+class WindowListenerService {
+ late SharedPreferences _preferences;
+
+ WindowListenerService._privateConstructor();
+
+ static final WindowListenerService instance =
+ WindowListenerService._privateConstructor();
+
+ Future init() async {
+ _preferences = await SharedPreferences.getInstance();
+ }
+
+ Size getWindowSize() {
+ final double windowWidth = _preferences.getDouble('windowWidth') ?? 450.0;
+ final double windowHeight = _preferences.getDouble('windowHeight') ?? 800.0;
+ return Size(windowWidth, windowHeight);
+ }
+
+ Future onWindowResize() async {
+ // Save the window size to shared preferences
+ await _preferences.setDouble(
+ 'windowWidth',
+ (await windowManager.getSize()).width,
+ );
+ await _preferences.setDouble(
+ 'windowHeight',
+ (await windowManager.getSize()).height,
+ );
+ }
+}
diff --git a/auth/lib/store/authenticator_db.dart b/auth/lib/store/authenticator_db.dart
index 0f05e83c0..deb57bc81 100644
--- a/auth/lib/store/authenticator_db.dart
+++ b/auth/lib/store/authenticator_db.dart
@@ -3,10 +3,12 @@ import 'dart:io';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
+import 'package:ente_auth/utils/directory_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class AuthenticatorDB {
static const _databaseName = "ente.authenticator.db";
@@ -25,6 +27,16 @@ class AuthenticatorDB {
}
Future _initDatabase() async {
+ if (Platform.isWindows || Platform.isLinux) {
+ var databaseFactory = databaseFactoryFfi;
+ return await databaseFactory.openDatabase(
+ await DirectoryUtils.getDatabasePath(_databaseName),
+ options: OpenDatabaseOptions(
+ version: _databaseVersion,
+ onCreate: _onCreate,
+ ),
+ );
+ }
final Directory documentsDirectory =
await getApplicationDocumentsDirectory();
final String path = join(documentsDirectory.path, _databaseName);
@@ -166,7 +178,7 @@ class AuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]);
}
}
- await batch.commit();
+ final _ = await batch.commit();
debugPrint("Done");
}
diff --git a/auth/lib/store/offline_authenticator_db.dart b/auth/lib/store/offline_authenticator_db.dart
index 402d6590c..d1af51fff 100644
--- a/auth/lib/store/offline_authenticator_db.dart
+++ b/auth/lib/store/offline_authenticator_db.dart
@@ -3,10 +3,11 @@ import 'dart:io';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
+import 'package:ente_auth/utils/directory_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
-import 'package:sqflite/sqflite.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class OfflineAuthenticatorDB {
static const _databaseName = "ente.offline_authenticator.db";
@@ -26,6 +27,16 @@ class OfflineAuthenticatorDB {
}
Future _initDatabase() async {
+ if (Platform.isWindows || Platform.isLinux) {
+ var databaseFactory = databaseFactoryFfi;
+ return await databaseFactory.openDatabase(
+ await DirectoryUtils.getDatabasePath(_databaseName),
+ options: OpenDatabaseOptions(
+ version: _databaseVersion,
+ onCreate: _onCreate,
+ ),
+ );
+ }
final Directory documentsDirectory =
await getApplicationDocumentsDirectory();
final String path = join(documentsDirectory.path, _databaseName);
@@ -152,7 +163,7 @@ class OfflineAuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]);
}
}
- await batch.commit();
+ final _ = await batch.commit();
debugPrint("Done");
}
diff --git a/auth/lib/theme/colors.dart b/auth/lib/theme/colors.dart
index 1d4a8517c..9ac9d2d7e 100644
--- a/auth/lib/theme/colors.dart
+++ b/auth/lib/theme/colors.dart
@@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
const Color _warning800 = Color(0xFFF53434);
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
+// ignore: unused_element
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
diff --git a/auth/lib/ui/account/change_email_dialog.dart b/auth/lib/ui/account/change_email_dialog.dart
index 7cfa10f4a..828970cd7 100644
--- a/auth/lib/ui/account/change_email_dialog.dart
+++ b/auth/lib/ui/account/change_email_dialog.dart
@@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
import 'package:flutter/material.dart';
class ChangeEmailDialog extends StatefulWidget {
- const ChangeEmailDialog({Key? key}) : super(key: key);
+ const ChangeEmailDialog({super.key});
@override
State createState() => _ChangeEmailDialogState();
diff --git a/auth/lib/ui/account/delete_account_page.dart b/auth/lib/ui/account/delete_account_page.dart
index 056744974..b8779e8cb 100644
--- a/auth/lib/ui/account/delete_account_page.dart
+++ b/auth/lib/ui/account/delete_account_page.dart
@@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/common/dialogs.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/email_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class DeleteAccountPage extends StatelessWidget {
const DeleteAccountPage({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
l10n.initiateAccountDeleteTitle,
);
+ await PlatformUtil.refocusWindows();
+
if (hasAuthenticated) {
final choice = await showChoiceDialogOld(
context,
@@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
return;
}
final decryptChallenge = CryptoUtil.openSealSync(
- Sodium.base642bin(response.encryptedChallenge),
- Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
+ CryptoUtil.base642bin(response.encryptedChallenge),
+ CryptoUtil.base642bin(
+ Configuration.instance.getKeyAttributes()!.publicKey,
+ ),
Configuration.instance.getSecretKey()!,
);
final challengeResponseStr = utf8.decode(decryptChallenge);
diff --git a/auth/lib/ui/account/email_entry_page.dart b/auth/lib/ui/account/email_entry_page.dart
index a20f6e245..e728ecd8c 100644
--- a/auth/lib/ui/account/email_entry_page.dart
+++ b/auth/lib/ui/account/email_entry_page.dart
@@ -5,7 +5,7 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -14,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart';
import "package:styled_text/styled_text.dart";
class EmailEntryPage extends StatefulWidget {
- const EmailEntryPage({Key? key}) : super(key: key);
+ const EmailEntryPage({super.key});
@override
State createState() => _EmailEntryPageState();
@@ -190,6 +190,7 @@ class _EmailEntryPageState extends State {
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
keyboardType: TextInputType.text,
+ textInputAction: TextInputAction.next,
controller: _passwordController1,
obscureText: !_password1Visible,
enableSuggestions: true,
@@ -427,15 +428,10 @@ class _EmailEntryPageState extends State {
tags: {
'u-terms': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.termsOfServicesTitle,
- "https://ente.io/terms",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.termsOfServicesTitle,
+ "https://ente.io/terms",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -443,15 +439,10 @@ class _EmailEntryPageState extends State {
),
'u-policy': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.privacyPolicyTitle,
- "https://ente.io/privacy",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.privacyPolicyTitle,
+ "https://ente.io/privacy",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -494,15 +485,10 @@ class _EmailEntryPageState extends State {
tags: {
'underline': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.encryption,
- "https://ente.io/architecture",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.encryption,
+ "https://ente.io/architecture",
),
style: const TextStyle(
decoration: TextDecoration.underline,
diff --git a/auth/lib/ui/account/login_page.dart b/auth/lib/ui/account/login_page.dart
index df28f98a1..5b8aa0daa 100644
--- a/auth/lib/ui/account/login_page.dart
+++ b/auth/lib/ui/account/login_page.dart
@@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import "package:styled_text/styled_text.dart";
class LoginPage extends StatefulWidget {
- const LoginPage({Key? key}) : super(key: key);
+ const LoginPage({super.key});
@override
State createState() => _LoginPageState();
@@ -25,6 +25,36 @@ class _LoginPageState extends State {
Color? _emailInputFieldColor;
final Logger _logger = Logger('_LoginPageState');
+ Future onPressed() async {
+ await UserService.instance.setEmail(_email!);
+ Configuration.instance.resetVolatilePassword();
+ SrpAttributes? attr;
+ bool isEmailVerificationEnabled = true;
+ try {
+ attr = await UserService.instance.getSrpAttributes(_email!);
+ isEmailVerificationEnabled = attr.isEmailMFAEnabled;
+ } catch (e) {
+ if (e is! SrpSetupNotCompleteError) {
+ _logger.severe('Error getting SRP attributes', e);
+ }
+ }
+ if (attr != null && !isEmailVerificationEnabled) {
+ await Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (BuildContext context) {
+ return LoginPasswordVerificationPage(
+ srpAttributes: attr!,
+ );
+ },
+ ),
+ );
+ } else {
+ await UserService.instance
+ .sendOtt(context, _email!, isCreateAccountScreen: false);
+ }
+ FocusScope.of(context).unfocus();
+ }
+
@override
void initState() {
_email = _config.getEmail();
@@ -60,36 +90,7 @@ class _LoginPageState extends State {
isKeypadOpen: isKeypadOpen,
isFormValid: _emailIsValid,
buttonText: context.l10n.logInLabel,
- onPressedFunction: () async {
- await UserService.instance.setEmail(_email!);
- Configuration.instance.resetVolatilePassword();
- SrpAttributes? attr;
- bool isEmailVerificationEnabled = true;
- try {
- attr = await UserService.instance.getSrpAttributes(_email!);
- isEmailVerificationEnabled = attr.isEmailMFAEnabled;
- } catch (e) {
- if (e is! SrpSetupNotCompleteError) {
- _logger.severe('Error getting SRP attributes', e);
- }
- }
- if (attr != null && !isEmailVerificationEnabled) {
- // ignore: unawaited_futures
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return LoginPasswordVerificationPage(
- srpAttributes: attr!,
- );
- },
- ),
- );
- } else {
- await UserService.instance
- .sendOtt(context, _email!, isCreateAccountScreen: false);
- }
- FocusScope.of(context).unfocus();
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -116,6 +117,8 @@ class _LoginPageState extends State {
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField(
autofillHints: const [AutofillHints.email],
+ onFieldSubmitted:
+ _emailIsValid ? (value) => onPressed() : null,
decoration: InputDecoration(
fillColor: _emailInputFieldColor,
filled: true,
@@ -179,15 +182,10 @@ class _LoginPageState extends State {
tags: {
'u-terms': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.termsOfServicesTitle,
- "https://ente.io/terms",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.termsOfServicesTitle,
+ "https://ente.io/terms",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -195,15 +193,10 @@ class _LoginPageState extends State {
),
'u-policy': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.privacyPolicyTitle,
- "https://ente.io/privacy",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.privacyPolicyTitle,
+ "https://ente.io/privacy",
),
style: const TextStyle(
decoration: TextDecoration.underline,
diff --git a/auth/lib/ui/account/login_pwd_verification_page.dart b/auth/lib/ui/account/login_pwd_verification_page.dart
index 3bd39a1b7..2d2754ec1 100644
--- a/auth/lib/ui/account/login_pwd_verification_page.dart
+++ b/auth/lib/ui/account/login_pwd_verification_page.dart
@@ -1,6 +1,5 @@
import "package:dio/dio.dart";
import 'package:ente_auth/core/configuration.dart';
-import "package:ente_auth/core/errors.dart";
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/models/api/user/srp.dart";
import "package:ente_auth/services/user_service.dart";
@@ -9,6 +8,7 @@ import 'package:ente_auth/ui/common/dynamic_fab.dart';
import "package:ente_auth/ui/components/buttons/button_widget.dart";
import "package:ente_auth/utils/dialog_util.dart";
import "package:ente_auth/utils/email_util.dart";
+import "package:ente_crypto_dart/ente_crypto_dart.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
@@ -19,8 +19,7 @@ import "package:logging/logging.dart";
// volatile password.
class LoginPasswordVerificationPage extends StatefulWidget {
final SrpAttributes srpAttributes;
- const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
- : super(key: key);
+ const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
@override
State createState() =>
@@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
bool _passwordInFocus = false;
bool _passwordVisible = false;
+ Future onPressed() async {
+ FocusScope.of(context).unfocus();
+ await verifyPassword(context, _passwordController.text);
+ }
+
@override
void initState() {
super.initState();
@@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState
isKeypadOpen: isKeypadOpen,
isFormValid: _passwordController.text.isNotEmpty,
buttonText: context.l10n.logInLabel,
- onPressedFunction: () async {
- FocusScope.of(context).unfocus();
- await verifyPassword(context, _passwordController.text);
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState
password,
dialog,
);
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
@@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState
);
} else {
_logger.severe('API failure during SRP login', e, s);
- if (e.type == DioErrorType.other) {
+ if (e.type == DioExceptionType.unknown) {
await _showContactSupportDialog(
context,
context.l10n.noInternetConnection,
@@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState
Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField(
+ onFieldSubmitted: _passwordController.text.isNotEmpty
+ ? (_) => onPressed()
+ : null,
key: const ValueKey("passwordInputField"),
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
diff --git a/auth/lib/ui/account/ott_verification_page.dart b/auth/lib/ui/account/ott_verification_page.dart
index 8bcaf3e34..cc9661def 100644
--- a/auth/lib/ui/account/ott_verification_page.dart
+++ b/auth/lib/ui/account/ott_verification_page.dart
@@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
this.isChangeEmail = false,
this.isCreateAccountScreen = false,
this.isResetPasswordScreen = false,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _OTTVerificationPageState();
@@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
class _OTTVerificationPageState extends State {
final _verificationCodeController = TextEditingController();
+ Future onPressed() async {
+ if (widget.isChangeEmail) {
+ await UserService.instance.changeEmail(
+ context,
+ widget.email,
+ _verificationCodeController.text,
+ );
+ } else {
+ await UserService.instance.verifyEmail(
+ context,
+ _verificationCodeController.text,
+ isResettingPasswordScreen: widget.isResetPasswordScreen,
+ );
+ }
+ FocusScope.of(context).unfocus();
+ }
+
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
@@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State {
body: _getBody(),
floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen,
- isFormValid: !(_verificationCodeController.text.isEmpty),
+ isFormValid: _verificationCodeController.text.isNotEmpty,
buttonText: l10n.verify,
- onPressedFunction: () {
- if (widget.isChangeEmail) {
- UserService.instance.changeEmail(
- context,
- widget.email,
- _verificationCodeController.text,
- );
- } else {
- UserService.instance
- .verifyEmail(context, _verificationCodeController.text,
- isResettingPasswordScreen: widget.isResetPasswordScreen,);
- }
- FocusScope.of(context).unfocus();
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State {
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
child: TextFormField(
style: Theme.of(context).textTheme.titleMedium,
+ onFieldSubmitted: _verificationCodeController.text.isNotEmpty
+ ? (_) => onPressed()
+ : null,
decoration: InputDecoration(
filled: true,
hintText: l10n.tapToEnterCode,
diff --git a/auth/lib/ui/account/password_entry_page.dart b/auth/lib/ui/account/password_entry_page.dart
index 7411d34a8..9b1ce6181 100644
--- a/auth/lib/ui/account/password_entry_page.dart
+++ b/auth/lib/ui/account/password_entry_page.dart
@@ -4,11 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/account/recovery_key_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -25,7 +25,7 @@ enum PasswordEntryMode {
class PasswordEntryPage extends StatefulWidget {
final PasswordEntryMode mode;
- const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
+ const PasswordEntryPage({required this.mode, super.key});
@override
State createState() => _PasswordEntryPageState();
@@ -149,227 +149,239 @@ class _PasswordEntryPageState extends State {
children: [
Expanded(
child: AutofillGroup(
- child: ListView(
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
- child: Text(
- buttonTextAndHeading,
- style: Theme.of(context).textTheme.headlineMedium,
+ child: FocusTraversalGroup(
+ policy: OrderedTraversalPolicy(),
+ child: ListView(
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 30,
+ horizontal: 20,
+ ),
+ child: Text(
+ buttonTextAndHeading,
+ style: Theme.of(context).textTheme.headlineMedium,
+ ),
),
- ),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- child: Text(
- widget.mode == PasswordEntryMode.set
- ? context.l10n.enterPasswordToEncrypt
- : context.l10n.enterNewPasswordToEncrypt,
- textAlign: TextAlign.start,
- style: Theme.of(context)
- .textTheme
- .titleMedium!
- .copyWith(fontSize: 14),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: Text(
+ widget.mode == PasswordEntryMode.set
+ ? context.l10n.enterPasswordToEncrypt
+ : context.l10n.enterNewPasswordToEncrypt,
+ textAlign: TextAlign.start,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(fontSize: 14),
+ ),
),
- ),
- const Padding(padding: EdgeInsets.all(8)),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- child: StyledText(
- text: context.l10n.passwordWarning,
- style: Theme.of(context)
- .textTheme
- .titleMedium!
- .copyWith(fontSize: 14),
- tags: {
- 'underline': StyledTextTag(
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
+ const Padding(padding: EdgeInsets.all(8)),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: StyledText(
+ text: context.l10n.passwordWarning,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(fontSize: 14),
+ tags: {
+ 'underline': StyledTextTag(
+ style:
+ Theme.of(context).textTheme.titleMedium!.copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
+ },
+ ),
+ ),
+ const Padding(padding: EdgeInsets.all(12)),
+ Visibility(
+ // hidden textForm for suggesting auto-fill service for saving
+ // password
+ visible: false,
+ child: TextFormField(
+ autofillHints: const [
+ AutofillHints.email,
+ ],
+ autocorrect: false,
+ keyboardType: TextInputType.emailAddress,
+ initialValue: email,
+ textInputAction: TextInputAction.next,
+ ),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
+ child: TextFormField(
+ autofillHints: const [AutofillHints.newPassword],
+ onFieldSubmitted: (_) {
+ do {
+ FocusScope.of(context).nextFocus();
+ } while (FocusScope.of(context).focusedChild!.context ==
+ null);
+ },
+ decoration: InputDecoration(
+ fillColor:
+ _isPasswordValid ? _validFieldValueColor : null,
+ filled: true,
+ hintText: context.l10n.password,
+ contentPadding: const EdgeInsets.all(20),
+ border: UnderlineInputBorder(
+ borderSide: BorderSide.none,
+ borderRadius: BorderRadius.circular(6),
+ ),
+ suffixIcon: _password1InFocus
+ ? IconButton(
+ icon: Icon(
+ _password1Visible
+ ? Icons.visibility
+ : Icons.visibility_off,
+ color: Theme.of(context).iconTheme.color,
+ size: 20,
),
+ onPressed: () {
+ setState(() {
+ _password1Visible = !_password1Visible;
+ });
+ },
+ )
+ : _isPasswordValid
+ ? Icon(
+ Icons.check,
+ color: Theme.of(context)
+ .inputDecorationTheme
+ .focusedBorder!
+ .borderSide
+ .color,
+ )
+ : null,
),
- },
- ),
- ),
- const Padding(padding: EdgeInsets.all(12)),
- Visibility(
- // hidden textForm for suggesting auto-fill service for saving
- // password
- visible: false,
- child: TextFormField(
- autofillHints: const [
- AutofillHints.email,
- ],
- autocorrect: false,
- keyboardType: TextInputType.emailAddress,
- initialValue: email,
- textInputAction: TextInputAction.next,
- ),
- ),
- Padding(
- padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
- child: TextFormField(
- autofillHints: const [AutofillHints.newPassword],
- decoration: InputDecoration(
- fillColor:
- _isPasswordValid ? _validFieldValueColor : null,
- filled: true,
- hintText: context.l10n.password,
- contentPadding: const EdgeInsets.all(20),
- border: UnderlineInputBorder(
- borderSide: BorderSide.none,
- borderRadius: BorderRadius.circular(6),
- ),
- suffixIcon: _password1InFocus
- ? IconButton(
- icon: Icon(
- _password1Visible
- ? Icons.visibility
- : Icons.visibility_off,
- color: Theme.of(context).iconTheme.color,
- size: 20,
- ),
- onPressed: () {
- setState(() {
- _password1Visible = !_password1Visible;
- });
- },
- )
- : _isPasswordValid
- ? Icon(
- Icons.check,
- color: Theme.of(context)
- .inputDecorationTheme
- .focusedBorder!
- .borderSide
- .color,
- )
- : null,
- ),
- obscureText: !_password1Visible,
- controller: _passwordController1,
- autofocus: false,
- autocorrect: false,
- keyboardType: TextInputType.visiblePassword,
- onChanged: (password) {
- setState(() {
- _passwordInInputBox = password;
- _passwordStrength = estimatePasswordStrength(password);
- _isPasswordValid =
- _passwordStrength >= kMildPasswordStrengthThreshold;
- _passwordsMatch = _passwordInInputBox ==
- _passwordInInputConfirmationBox;
- });
- },
- textInputAction: TextInputAction.next,
- focusNode: _password1FocusNode,
- ),
- ),
- const SizedBox(height: 8),
- Padding(
- padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
- child: TextFormField(
- keyboardType: TextInputType.visiblePassword,
- controller: _passwordController2,
- obscureText: !_password2Visible,
- autofillHints: const [AutofillHints.newPassword],
- onEditingComplete: () => TextInput.finishAutofillContext(),
- decoration: InputDecoration(
- fillColor: _passwordsMatch ? _validFieldValueColor : null,
- filled: true,
- hintText: context.l10n.confirmPassword,
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 20,
- ),
- suffixIcon: _password2InFocus
- ? IconButton(
- icon: Icon(
- _password2Visible
- ? Icons.visibility
- : Icons.visibility_off,
- color: Theme.of(context).iconTheme.color,
- size: 20,
- ),
- onPressed: () {
- setState(() {
- _password2Visible = !_password2Visible;
- });
- },
- )
- : _passwordsMatch
- ? Icon(
- Icons.check,
- color: Theme.of(context)
- .inputDecorationTheme
- .focusedBorder!
- .borderSide
- .color,
- )
- : null,
- border: UnderlineInputBorder(
- borderSide: BorderSide.none,
- borderRadius: BorderRadius.circular(6),
- ),
- ),
- focusNode: _password2FocusNode,
- onChanged: (cnfPassword) {
- setState(() {
- _passwordInInputConfirmationBox = cnfPassword;
- if (_passwordInInputBox != '') {
+ obscureText: !_password1Visible,
+ controller: _passwordController1,
+ autofocus: false,
+ autocorrect: false,
+ keyboardType: TextInputType.visiblePassword,
+ onChanged: (password) {
+ setState(() {
+ _passwordInInputBox = password;
+ _passwordStrength =
+ estimatePasswordStrength(password);
+ _isPasswordValid = _passwordStrength >=
+ kMildPasswordStrengthThreshold;
_passwordsMatch = _passwordInInputBox ==
_passwordInInputConfirmationBox;
- }
- });
- },
- ),
- ),
- Opacity(
- opacity:
- (_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
- child: Text(
- context.l10n.passwordStrength(passwordStrengthText),
- style: TextStyle(
- color: passwordStrengthColor,
- ),
+ });
+ },
+ textInputAction: TextInputAction.next,
+ focusNode: _password1FocusNode,
),
),
- ),
- const SizedBox(height: 8),
- GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: () {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.howItWorks,
- "https://ente.io/architecture",
- );
- },
- ),
- );
- },
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 20),
- child: RichText(
- text: TextSpan(
- text: context.l10n.howItWorks,
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
+ const SizedBox(height: 8),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
+ child: TextFormField(
+ keyboardType: TextInputType.visiblePassword,
+ controller: _passwordController2,
+ obscureText: !_password2Visible,
+ autofillHints: const [AutofillHints.newPassword],
+ onEditingComplete: () =>
+ TextInput.finishAutofillContext(),
+ decoration: InputDecoration(
+ fillColor:
+ _passwordsMatch ? _validFieldValueColor : null,
+ filled: true,
+ hintText: context.l10n.confirmPassword,
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 20,
+ ),
+ suffixIcon: _password2InFocus
+ ? IconButton(
+ icon: Icon(
+ _password2Visible
+ ? Icons.visibility
+ : Icons.visibility_off,
+ color: Theme.of(context).iconTheme.color,
+ size: 20,
),
+ onPressed: () {
+ setState(() {
+ _password2Visible = !_password2Visible;
+ });
+ },
+ )
+ : _passwordsMatch
+ ? Icon(
+ Icons.check,
+ color: Theme.of(context)
+ .inputDecorationTheme
+ .focusedBorder!
+ .borderSide
+ .color,
+ )
+ : null,
+ border: UnderlineInputBorder(
+ borderSide: BorderSide.none,
+ borderRadius: BorderRadius.circular(6),
+ ),
+ ),
+ focusNode: _password2FocusNode,
+ onChanged: (cnfPassword) {
+ setState(() {
+ _passwordInInputConfirmationBox = cnfPassword;
+ if (_passwordInInputBox != '') {
+ _passwordsMatch = _passwordInInputBox ==
+ _passwordInInputConfirmationBox;
+ }
+ });
+ },
+ ),
+ ),
+ Opacity(
+ opacity: (_passwordInInputBox != '') && _password1InFocus
+ ? 1
+ : 0,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 8,
+ ),
+ child: Text(
+ context.l10n.passwordStrength(passwordStrengthText),
+ style: TextStyle(
+ color: passwordStrengthColor,
+ ),
),
),
),
- ),
- const Padding(padding: EdgeInsets.all(20)),
- ],
+ const SizedBox(height: 8),
+ GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onTap: () {
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.howItWorks,
+ "https://ente.io/architecture",
+ );
+ },
+ child: Container(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: RichText(
+ text: TextSpan(
+ text: context.l10n.howItWorks,
+ style:
+ Theme.of(context).textTheme.titleMedium!.copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
+ ),
+ ),
+ ),
+ const Padding(padding: EdgeInsets.all(20)),
+ ],
+ ),
),
),
),
@@ -463,6 +475,7 @@ class _PasswordEntryPageState extends State {
showGenericErrorDialog(context: context);
}
}
+
// ignore: unawaited_futures
routeToPage(
context,
diff --git a/auth/lib/ui/account/password_reentry_page.dart b/auth/lib/ui/account/password_reentry_page.dart
index f729f935e..261f41db5 100644
--- a/auth/lib/ui/account/password_reentry_page.dart
+++ b/auth/lib/ui/account/password_reentry_page.dart
@@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/home_page.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/email_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class PasswordReentryPage extends StatefulWidget {
- const PasswordReentryPage({Key? key}) : super(key: key);
+ const PasswordReentryPage({super.key});
@override
State createState() => _PasswordReentryPageState();
@@ -261,8 +261,8 @@ class _PasswordReentryPageState extends State {
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
- child: Wrap(
- alignment: WrapAlignment.spaceBetween,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
@@ -275,13 +275,17 @@ class _PasswordReentryPageState extends State {
),
);
},
- child: Text(
- context.l10n.forgotPassword,
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ child: Center(
+ child: Text(
+ context.l10n.forgotPassword,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
),
),
GestureDetector(
@@ -297,13 +301,17 @@ class _PasswordReentryPageState extends State {
Navigator.of(context)
.popUntil((route) => route.isFirst);
},
- child: Text(
- context.l10n.changeEmail,
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ child: Center(
+ child: Text(
+ context.l10n.changeEmail,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
),
),
],
diff --git a/auth/lib/ui/account/recovery_key_page.dart b/auth/lib/ui/account/recovery_key_page.dart
index c5ca66fdc..a74b2daec 100644
--- a/auth/lib/ui/account/recovery_key_page.dart
+++ b/auth/lib/ui/account/recovery_key_page.dart
@@ -1,3 +1,4 @@
+import 'dart:convert';
import 'dart:io' as io;
import 'package:bip39/bip39.dart' as bip39;
@@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/share_utils.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:file_saver/file_saver.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
@@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
const RecoveryKeyPage(
this.recoveryKey,
this.doneText, {
- Key? key,
+ super.key,
this.showAppBar,
this.onDone,
this.isDismissible,
@@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
this.text,
this.subText,
this.showProgressBar = false,
- }) : super(key: key);
+ });
@override
State createState() => _RecoveryKeyPageState();
@@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
class _RecoveryKeyPageState extends State {
bool _hasTriedToSave = false;
final _recoveryKeyFile = io.File(
- Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
+ "${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
);
@override
@@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State {
? 32
: 120;
+ Future copy() async {
+ await Clipboard.setData(
+ ClipboardData(
+ text: recoveryKey,
+ ),
+ );
+ showShortToast(
+ context,
+ context.l10n.recoveryKeyCopiedToClipboard,
+ );
+ setState(() {
+ _hasTriedToSave = true;
+ });
+ }
+
return Scaffold(
appBar: widget.showProgressBar
? AppBar(
@@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State {
style: Theme.of(context).textTheme.titleMedium,
),
const Padding(padding: EdgeInsets.only(top: 24)),
- DottedBorder(
- color: const Color.fromARGB(255, 105, 17, 127),
- //color of dotted/dash line
- strokeWidth: 1,
- //thickness of dash/dots
- dashPattern: const [6, 6],
- radius: const Radius.circular(8),
- //dash patterns, 10 is dash width, 6 is space width
- child: SizedBox(
- //inner container
- // height: 120, //height of inner container
- width: double
- .infinity, //width to 100% match to parent container.
- // ignore: prefer_const_literals_to_create_immutables
- child: Column(
- children: [
- GestureDetector(
- onTap: () async {
- await Clipboard.setData(
- ClipboardData(text: recoveryKey),
- );
- showShortToast(
- context,
- context.l10n.recoveryKeyCopiedToClipboard,
- );
- setState(() {
- _hasTriedToSave = true;
- });
- },
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(
- color: const Color.fromRGBO(
- 49,
- 155,
- 86,
- .2,
- ),
+ Container(
+ padding: const EdgeInsets.all(1),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ gradient: const LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Color(0x8E9610D6),
+ Color(0x8E9F4FC6),
+ ],
+ stops: [0.0, 0.9753],
+ ),
+ ),
+ child: DottedBorder(
+ padding: EdgeInsets.zero,
+ borderType: BorderType.RRect,
+ strokeWidth: 1,
+ color: const Color(0xFF6B6B6B),
+ dashPattern: const [6, 6],
+ radius: const Radius.circular(8),
+ child: SizedBox(
+ width: double.infinity,
+ child: Stack(
+ children: [
+ Column(
+ children: [
+ Builder(
+ builder: (context) {
+ final content = Container(
+ padding: const EdgeInsets.all(20),
+ width: double.infinity,
+ child: Text(
+ recoveryKey,
+ textAlign: TextAlign.justify,
+ style: Theme.of(context)
+ .textTheme
+ .bodyLarge,
+ ),
+ );
+
+ if (PlatformUtil.isMobile()) {
+ return GestureDetector(
+ onTap: () async => await copy(),
+ child: content,
+ );
+ } else {
+ return SelectableRegion(
+ focusNode: FocusNode(),
+ selectionControls:
+ PlatformUtil.selectionControls,
+ child: content,
+ );
+ }
+ },
),
- borderRadius: const BorderRadius.all(
- Radius.circular(2),
- ),
- color: Theme.of(context)
- .colorScheme
- .recoveryKeyBoxColor,
- ),
- padding: const EdgeInsets.all(20),
- width: double.infinity,
- child: Text(
- recoveryKey,
- style:
- Theme.of(context).textTheme.bodyLarge,
+ ],
+ ),
+ Positioned(
+ right: 0,
+ top: 0,
+ child: PlatformCopy(
+ onPressed: copy,
),
),
- ),
- ],
+ ],
+ ),
),
),
),
@@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State {
),
),
],
- ), // columnEnds
+ ),
),
),
);
@@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State {
final List childrens = [];
if (!_hasTriedToSave) {
childrens.add(
- ElevatedButton(
- style: Theme.of(context).colorScheme.optionalActionButtonStyle,
- onPressed: () async {
- await _saveKeys();
- },
- child: Text(context.l10n.doThisLater),
+ SizedBox(
+ height: 56,
+ child: ElevatedButton(
+ style: Theme.of(context).colorScheme.optionalActionButtonStyle,
+ onPressed: () async {
+ await _saveKeys();
+ },
+ child: Text(context.l10n.doThisLater),
+ ),
),
);
childrens.add(const SizedBox(height: 10));
@@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State {
childrens.add(
GradientButton(
onTap: () async {
- await _shareRecoveryKey(recoveryKey);
+ await shareDialog(
+ context,
+ context.l10n.recoveryKey,
+ saveAction: () async {
+ await _saveRecoveryKey(recoveryKey);
+ },
+ sendAction: () async {
+ await _shareRecoveryKey(recoveryKey);
+ },
+ );
},
text: context.l10n.saveKey,
),
);
+
if (_hasTriedToSave) {
childrens.add(const SizedBox(height: 10));
childrens.add(
- ElevatedButton(
- child: Text(widget.doneText),
- onPressed: () async {
- await _saveKeys();
- },
+ SizedBox(
+ height: 56,
+ child: ElevatedButton(
+ child: Text(widget.doneText),
+ onPressed: () async {
+ await _saveKeys();
+ },
+ ),
),
);
}
@@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State {
return childrens;
}
+ Future _saveRecoveryKey(String recoveryKey) async {
+ final bytes = utf8.encode(recoveryKey);
+ final time = DateTime.now().millisecondsSinceEpoch;
+
+ await PlatformUtil.shareFile(
+ "ente_recovery_key_$time",
+ "txt",
+ bytes,
+ MimeType.text,
+ );
+
+ if (mounted) {
+ showToast(
+ context,
+ context.l10n.recoveryKeySaved,
+ );
+ setState(() {
+ _hasTriedToSave = true;
+ });
+ }
+ }
+
Future _shareRecoveryKey(String recoveryKey) async {
if (_recoveryKeyFile.existsSync()) {
await _recoveryKeyFile.delete();
}
_recoveryKeyFile.writeAsStringSync(recoveryKey);
+ // ignore: deprecated_member_use
await Share.shareFiles([_recoveryKeyFile.path]);
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) {
@@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State {
widget.onDone!();
}
}
+
+class PlatformCopy extends StatelessWidget {
+ const PlatformCopy({
+ super.key,
+ required this.onPressed,
+ });
+
+ final void Function() onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return IconButton(
+ onPressed: () => onPressed(),
+ visualDensity: VisualDensity.compact,
+ icon: const Icon(
+ Icons.copy,
+ size: 16,
+ ),
+ );
+ }
+}
diff --git a/auth/lib/ui/account/recovery_page.dart b/auth/lib/ui/account/recovery_page.dart
index f0d5199d1..137b8ce43 100644
--- a/auth/lib/ui/account/recovery_page.dart
+++ b/auth/lib/ui/account/recovery_page.dart
@@ -1,8 +1,5 @@
-
-
-import 'dart:ui';
-
import 'package:ente_auth/core/configuration.dart';
+import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/utils/dialog_util.dart';
@@ -10,7 +7,7 @@ import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class RecoveryPage extends StatefulWidget {
- const RecoveryPage({Key? key}) : super(key: key);
+ const RecoveryPage({super.key});
@override
State createState() => _RecoveryPageState();
@@ -19,6 +16,36 @@ class RecoveryPage extends StatefulWidget {
class _RecoveryPageState extends State {
final _recoveryKey = TextEditingController();
+ Future onPressed() async {
+ FocusScope.of(context).unfocus();
+ final dialog = createProgressDialog(context, "Decrypting...");
+ await dialog.show();
+ try {
+ await Configuration.instance.recover(_recoveryKey.text.trim());
+ await dialog.hide();
+ showToast(context, "Recovery successful!");
+ await Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (BuildContext context) {
+ return const PopScope(
+ canPop: false,
+ child: PasswordEntryPage(
+ mode: PasswordEntryMode.reset,
+ ),
+ );
+ },
+ ),
+ );
+ } catch (e) {
+ await dialog.hide();
+ String errMessage = 'The recovery key you entered is incorrect';
+ if (e is AssertionError) {
+ errMessage = '$errMessage : ${e.message}';
+ }
+ await showErrorDialog(context, "Incorrect recovery key", errMessage);
+ }
+ }
+
@override
Widget build(BuildContext context) {
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
@@ -46,37 +73,7 @@ class _RecoveryPageState extends State {
isKeypadOpen: isKeypadOpen,
isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover',
- onPressedFunction: () async {
- FocusScope.of(context).unfocus();
- final dialog = createProgressDialog(context, "Decrypting...");
- await dialog.show();
- try {
- await Configuration.instance.recover(_recoveryKey.text.trim());
- await dialog.hide();
- showToast(context, "Recovery successful!");
- // ignore: unawaited_futures
- Navigator.of(context).pushReplacement(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WillPopScope(
- onWillPop: () async => false,
- child: const PasswordEntryPage(
- mode: PasswordEntryMode.reset,
- ),
- );
- },
- ),
- );
- } catch (e) {
- await dialog.hide();
- String errMessage = 'The recovery key you entered is incorrect';
- if (e is AssertionError) {
- errMessage = '$errMessage : ${e.message}';
- }
- // ignore: unawaited_futures
- showErrorDialog(context, "Incorrect recovery key", errMessage);
- }
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -89,7 +86,7 @@ class _RecoveryPageState extends State {
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text(
- 'Forgot password',
+ context.l10n.forgotPassword,
style: Theme.of(context).textTheme.headlineMedium,
),
),
@@ -140,12 +137,14 @@ class _RecoveryPageState extends State {
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Center(
child: Text(
- "No recovery key?",
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ context.l10n.noRecoveryKeyTitle,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
),
),
),
diff --git a/auth/lib/ui/account/request_pwd_verification_page.dart b/auth/lib/ui/account/request_pwd_verification_page.dart
index d852859fa..5901d3bd4 100644
--- a/auth/lib/ui/account/request_pwd_verification_page.dart
+++ b/auth/lib/ui/account/request_pwd_verification_page.dart
@@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/theme/ente_theme.dart";
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import "package:ente_auth/utils/crypto_util.dart";
import "package:ente_auth/utils/dialog_util.dart";
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import "package:flutter_sodium/flutter_sodium.dart";
import "package:logging/logging.dart";
typedef OnPasswordVerifiedFn = Future Function(Uint8List bytes);
@@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
final OnPasswordVerifiedFn onPasswordVerified;
final Function? onPasswordError;
- const RequestPasswordVerificationPage(
- {super.key, required this.onPasswordVerified, this.onPasswordError,});
+ const RequestPasswordVerificationPage({
+ super.key,
+ required this.onPasswordVerified,
+ this.onPasswordError,
+ });
@override
State createState() =>
@@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
try {
final attributes = Configuration.instance.getKeyAttributes()!;
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
- utf8.encode(_passwordController.text) as Uint8List,
- Sodium.base642bin(attributes.kekSalt),
+ utf8.encode(_passwordController.text),
+ CryptoUtil.base642bin(attributes.kekSalt),
attributes.memLimit,
attributes.opsLimit,
);
CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedKey),
+ CryptoUtil.base642bin(attributes.encryptedKey),
keyEncryptionKey,
- Sodium.base642bin(attributes.keyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.keyDecryptionNonce),
);
await dialog.show();
// pop
diff --git a/auth/lib/ui/account/sessions_page.dart b/auth/lib/ui/account/sessions_page.dart
index 31b7e7a41..1815b20e2 100644
--- a/auth/lib/ui/account/sessions_page.dart
+++ b/auth/lib/ui/account/sessions_page.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class SessionsPage extends StatefulWidget {
- const SessionsPage({Key? key}) : super(key: key);
+ const SessionsPage({super.key});
@override
State createState() => _SessionsPageState();
diff --git a/auth/lib/ui/account/verify_recovery_page.dart b/auth/lib/ui/account/verify_recovery_page.dart
index 2633afe33..03ed81fdf 100644
--- a/auth/lib/ui/account/verify_recovery_page.dart
+++ b/auth/lib/ui/account/verify_recovery_page.dart
@@ -1,5 +1,3 @@
-import 'dart:ui';
-
import 'package:bip39/bip39.dart' as bip39;
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
@@ -12,12 +10,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
class VerifyRecoveryPage extends StatefulWidget {
- const VerifyRecoveryPage({Key? key}) : super(key: key);
+ const VerifyRecoveryPage({super.key});
@override
State createState() => _VerifyRecoveryPageState();
@@ -34,14 +33,14 @@ class _VerifyRecoveryPageState extends State {
try {
final String inputKey = _recoveryKey.text.trim();
final String recoveryKey =
- Sodium.bin2hex(Configuration.instance.getRecoveryKey());
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
try {
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
} catch (e) {
await dialog.hide();
- if (e is DioError && e.type == DioErrorType.other) {
+ if (e is DioException && e.type == DioExceptionType.unknown) {
await showErrorDialog(
context,
"No internet connection",
@@ -88,12 +87,14 @@ class _VerifyRecoveryPageState extends State {
context,
"Please authenticate to view your recovery key",
);
+ await PlatformUtil.refocusWindows();
+
if (hasAuthenticated) {
String recoveryKey;
try {
- recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
- // ignore: unawaited_futures
- routeToPage(
+ recoveryKey =
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
+ await routeToPage(
context,
RecoveryKeyPage(
recoveryKey,
diff --git a/auth/lib/ui/code_timer_progress.dart b/auth/lib/ui/code_timer_progress.dart
index 1dbb4358e..b524a0c23 100644
--- a/auth/lib/ui/code_timer_progress.dart
+++ b/auth/lib/ui/code_timer_progress.dart
@@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
final int period;
CodeTimerProgress({
- Key? key,
+ super.key,
required this.period,
- }) : super(key: key);
+ });
@override
- _CodeTimerProgressState createState() => _CodeTimerProgressState();
+ State createState() => _CodeTimerProgressState();
}
class _CodeTimerProgressState extends State
diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart
index 7e0f78be3..f97e865ec 100644
--- a/auth/lib/ui/code_widget.dart
+++ b/auth/lib/ui/code_widget.dart
@@ -14,9 +14,11 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/code_timer_progress.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:ente_auth/utils/totp_util.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_context_menu/flutter_context_menu.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
@@ -24,7 +26,7 @@ import 'package:move_to_background/move_to_background.dart';
class CodeWidget extends StatefulWidget {
final Code code;
- const CodeWidget(this.code, {Key? key}) : super(key: key);
+ const CodeWidget(this.code, {super.key});
@override
State createState() => _CodeWidgetState();
@@ -84,83 +86,121 @@ class _CodeWidgetState extends State {
final l10n = context.l10n;
return Container(
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
- child: Slidable(
- key: ValueKey(widget.code.hashCode),
- endActionPane: ActionPane(
- extentRatio: 0.60,
- motion: const ScrollMotion(),
- children: [
- const SizedBox(
- width: 4,
- ),
- SlidableAction(
- onPressed: _onShowQrPressed,
- backgroundColor: Colors.grey.withOpacity(0.1),
- borderRadius: const BorderRadius.all(Radius.circular(12.0)),
- foregroundColor:
- Theme.of(context).colorScheme.inverseBackgroundColor,
- icon: Icons.qr_code_2_outlined,
- label: "QR",
- padding: const EdgeInsets.only(left: 4, right: 0),
- spacing: 8,
- ),
- const SizedBox(
- width: 4,
- ),
- SlidableAction(
- onPressed: _onEditPressed,
- backgroundColor: Colors.grey.withOpacity(0.1),
- borderRadius: const BorderRadius.all(Radius.circular(12.0)),
- foregroundColor:
- Theme.of(context).colorScheme.inverseBackgroundColor,
- icon: Icons.edit_outlined,
- label: l10n.edit,
- padding: const EdgeInsets.only(left: 4, right: 0),
- spacing: 8,
- ),
- const SizedBox(
- width: 4,
- ),
- SlidableAction(
- onPressed: _onDeletePressed,
- backgroundColor: Colors.grey.withOpacity(0.1),
- borderRadius: const BorderRadius.all(Radius.circular(12.0)),
- foregroundColor: const Color(0xFFFE4A49),
- icon: Icons.delete,
- label: l10n.delete,
- padding: const EdgeInsets.only(left: 0, right: 0),
- spacing: 8,
- ),
- ],
- ),
- child: ClipRRect(
- borderRadius: BorderRadius.circular(8),
- child: Container(
- color: Theme.of(context).colorScheme.codeCardBackgroundColor,
- child: Material(
- color: Colors.transparent,
- child: InkWell(
- customBorder: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(10),
- ),
- onTap: () {
- _copyCurrentOTPToClipboard();
- },
- onDoubleTap: isMaskingEnabled
- ? () {
- setState(
- () {
- _hideCode = !_hideCode;
- },
- );
- }
- : null,
- onLongPress: () {
- _copyCurrentOTPToClipboard();
- },
- child: _getCardContents(l10n),
+ child: Builder(
+ builder: (context) {
+ if (PlatformUtil.isDesktop()) {
+ return ContextMenuRegion(
+ contextMenu: ContextMenu(
+ entries: [
+ MenuItem(
+ label: 'QR',
+ icon: Icons.qr_code_2_outlined,
+ onSelected: () => _onShowQrPressed(null),
+ ),
+ MenuItem(
+ label: l10n.edit,
+ icon: Icons.edit,
+ onSelected: () => _onEditPressed(null),
+ ),
+ const MenuDivider(),
+ MenuItem(
+ label: l10n.delete,
+ value: "Delete",
+ icon: Icons.delete,
+ onSelected: () => _onDeletePressed(null),
+ ),
+ ],
+ padding: const EdgeInsets.all(8.0),
),
+ child: _clippedCard(l10n),
+ );
+ }
+
+ return Slidable(
+ key: ValueKey(widget.code.hashCode),
+ endActionPane: ActionPane(
+ extentRatio: 0.60,
+ motion: const ScrollMotion(),
+ children: [
+ const SizedBox(
+ width: 4,
+ ),
+ SlidableAction(
+ onPressed: _onShowQrPressed,
+ backgroundColor: Colors.grey.withOpacity(0.1),
+ borderRadius: const BorderRadius.all(Radius.circular(12.0)),
+ foregroundColor:
+ Theme.of(context).colorScheme.inverseBackgroundColor,
+ icon: Icons.qr_code_2_outlined,
+ label: "QR",
+ padding: const EdgeInsets.only(left: 4, right: 0),
+ spacing: 8,
+ ),
+ const SizedBox(
+ width: 4,
+ ),
+ SlidableAction(
+ onPressed: _onEditPressed,
+ backgroundColor: Colors.grey.withOpacity(0.1),
+ borderRadius: const BorderRadius.all(Radius.circular(12.0)),
+ foregroundColor:
+ Theme.of(context).colorScheme.inverseBackgroundColor,
+ icon: Icons.edit_outlined,
+ label: l10n.edit,
+ padding: const EdgeInsets.only(left: 4, right: 0),
+ spacing: 8,
+ ),
+ const SizedBox(
+ width: 4,
+ ),
+ SlidableAction(
+ onPressed: _onDeletePressed,
+ backgroundColor: Colors.grey.withOpacity(0.1),
+ borderRadius: const BorderRadius.all(Radius.circular(12.0)),
+ foregroundColor: const Color(0xFFFE4A49),
+ icon: Icons.delete,
+ label: l10n.delete,
+ padding: const EdgeInsets.only(left: 0, right: 0),
+ spacing: 8,
+ ),
+ ],
),
+ child: Builder(
+ builder: (context) => _clippedCard(l10n),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ Widget _clippedCard(AppLocalizations l10n) {
+ return ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: Container(
+ color: Theme.of(context).colorScheme.codeCardBackgroundColor,
+ child: Material(
+ color: Colors.transparent,
+ child: InkWell(
+ customBorder: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ onTap: () {
+ _copyCurrentOTPToClipboard();
+ },
+ onDoubleTap: isMaskingEnabled
+ ? () {
+ setState(
+ () {
+ _hideCode = !_hideCode;
+ },
+ );
+ }
+ : null,
+ onLongPress: () {
+ _copyCurrentOTPToClipboard();
+ },
+ child: _getCardContents(l10n),
),
),
),
@@ -373,9 +413,10 @@ class _CodeWidgetState extends State {
}
Future _onEditPressed(_) async {
- bool _isAuthSuccessful = await LocalAuthenticationService.instance
+ bool isAuthSuccessful = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
- if (!_isAuthSuccessful) {
+ await PlatformUtil.refocusWindows();
+ if (!isAuthSuccessful) {
return;
}
final Code? code = await Navigator.of(context).push(
@@ -391,9 +432,10 @@ class _CodeWidgetState extends State {
}
Future _onShowQrPressed(_) async {
- bool _isAuthSuccessful = await LocalAuthenticationService.instance
+ bool isAuthSuccessful = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
- if (!_isAuthSuccessful) {
+ await PlatformUtil.refocusWindows();
+ if (!isAuthSuccessful) {
return;
}
// ignore: unused_local_variable
@@ -407,14 +449,15 @@ class _CodeWidgetState extends State {
}
void _onDeletePressed(_) async {
- bool _isAuthSuccessful =
+ bool isAuthSuccessful =
await LocalAuthenticationService.instance.requestLocalAuthentication(
context,
context.l10n.deleteCodeAuthMessage,
);
- if (!_isAuthSuccessful) {
+ if (!isAuthSuccessful) {
return;
}
+ FocusScope.of(context).requestFocus();
final l10n = context.l10n;
await showChoiceActionSheet(
context,
@@ -451,7 +494,7 @@ class _CodeWidgetState extends State {
code = code.replaceAll(RegExp(r'\d'), '•');
}
if (code.length == 6) {
- return code.substring(0, 3) + " " + code.substring(3, 6);
+ return "${code.substring(0, 3)} ${code.substring(3, 6)}";
}
return code;
}
diff --git a/auth/lib/ui/common/bottom_shadow.dart b/auth/lib/ui/common/bottom_shadow.dart
index 08a73564f..a57e5232a 100644
--- a/auth/lib/ui/common/bottom_shadow.dart
+++ b/auth/lib/ui/common/bottom_shadow.dart
@@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
class BottomShadowWidget extends StatelessWidget {
final double offsetDy;
final Color? shadowColor;
- const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
- : super(key: key);
+ const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/DividerWithPadding.dart b/auth/lib/ui/common/divider_with_padding.dart
similarity index 92%
rename from auth/lib/ui/common/DividerWithPadding.dart
rename to auth/lib/ui/common/divider_with_padding.dart
index 94b20d9c2..0470bdf34 100644
--- a/auth/lib/ui/common/DividerWithPadding.dart
+++ b/auth/lib/ui/common/divider_with_padding.dart
@@ -1,17 +1,15 @@
-
-
import 'package:flutter/material.dart';
class DividerWithPadding extends StatelessWidget {
final double left, top, right, bottom, thinckness;
const DividerWithPadding({
- Key? key,
+ super.key,
this.left = 0,
this.top = 0,
this.right = 0,
this.bottom = 0,
this.thinckness = 0.5,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/dynamic_fab.dart b/auth/lib/ui/common/dynamic_fab.dart
index 0f9771627..8ccd9b855 100644
--- a/auth/lib/ui/common/dynamic_fab.dart
+++ b/auth/lib/ui/common/dynamic_fab.dart
@@ -10,12 +10,12 @@ class DynamicFAB extends StatelessWidget {
final Function? onPressedFunction;
const DynamicFAB({
- Key? key,
+ super.key,
this.isKeypadOpen,
this.buttonText,
this.isFormValid,
this.onPressedFunction,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -60,6 +60,7 @@ class DynamicFAB extends StatelessWidget {
} else {
return Container(
width: double.infinity,
+ height: 56,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: OutlinedButton(
onPressed:
diff --git a/auth/lib/ui/common/gradient_button.dart b/auth/lib/ui/common/gradient_button.dart
index 5b021f613..8a24c6832 100644
--- a/auth/lib/ui/common/gradient_button.dart
+++ b/auth/lib/ui/common/gradient_button.dart
@@ -1,5 +1,3 @@
-
-
import 'package:flutter/material.dart';
class GradientButton extends StatelessWidget {
@@ -15,17 +13,21 @@ class GradientButton extends StatelessWidget {
// padding between the text and icon
final double paddingValue;
+ // used when two icons are in row
+ final bool reversedGradient;
+
const GradientButton({
- Key? key,
+ super.key,
this.linearGradientColors = const [
Color.fromARGB(255, 133, 44, 210),
Color.fromARGB(255, 187, 26, 93),
],
+ this.reversedGradient = false,
this.onTap,
this.text = '',
this.iconData,
this.paddingValue = 0.0,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -71,7 +73,9 @@ class GradientButton extends StatelessWidget {
gradient: LinearGradient(
begin: const Alignment(0.1, -0.9),
end: const Alignment(-0.6, 0.9),
- colors: linearGradientColors,
+ colors: reversedGradient
+ ? linearGradientColors.reversed.toList()
+ : linearGradientColors,
),
borderRadius: BorderRadius.circular(8),
),
diff --git a/auth/lib/ui/common/linear_progress_dialog.dart b/auth/lib/ui/common/linear_progress_dialog.dart
index 85bcc8c5b..08c46d6c9 100644
--- a/auth/lib/ui/common/linear_progress_dialog.dart
+++ b/auth/lib/ui/common/linear_progress_dialog.dart
@@ -1,12 +1,10 @@
-
-
import 'package:ente_auth/ente_theme_data.dart';
import 'package:flutter/material.dart';
class LinearProgressDialog extends StatefulWidget {
final String message;
- const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
+ const LinearProgressDialog(this.message, {super.key});
@override
LinearProgressDialogState createState() => LinearProgressDialogState();
@@ -29,8 +27,8 @@ class LinearProgressDialogState extends State {
@override
Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async => false,
+ return PopScope(
+ canPop: false,
child: AlertDialog(
title: Text(
widget.message,
diff --git a/auth/lib/ui/common/loading_widget.dart b/auth/lib/ui/common/loading_widget.dart
index cf38f3e6b..d37548620 100644
--- a/auth/lib/ui/common/loading_widget.dart
+++ b/auth/lib/ui/common/loading_widget.dart
@@ -11,8 +11,8 @@ class EnteLoadingWidget extends StatelessWidget {
this.size = 14,
this.padding = 5,
this.alignment = Alignment.center,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/progress_dialog.dart b/auth/lib/ui/common/progress_dialog.dart
index 269a2b9b3..adabff2ee 100644
--- a/auth/lib/ui/common/progress_dialog.dart
+++ b/auth/lib/ui/common/progress_dialog.dart
@@ -153,8 +153,8 @@ class ProgressDialog {
barrierColor: _barrierColor,
builder: (BuildContext context) {
_dismissingContext = context;
- return WillPopScope(
- onWillPop: () async => _barrierDismissible,
+ return PopScope(
+ canPop: _barrierDismissible,
child: Dialog(
backgroundColor: _backgroundColor,
insetAnimationCurve: _insetAnimCurve,
@@ -198,6 +198,7 @@ class _Body extends StatefulWidget {
@override
State createState() {
+ // ignore: no_logic_in_create_state
return _dialog;
}
}
diff --git a/auth/lib/ui/common/rename_dialog.dart b/auth/lib/ui/common/rename_dialog.dart
index 03c68d3b1..ad93d1aba 100644
--- a/auth/lib/ui/common/rename_dialog.dart
+++ b/auth/lib/ui/common/rename_dialog.dart
@@ -8,8 +8,7 @@ class RenameDialog extends StatefulWidget {
final String type;
final int maxLength;
- const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100})
- : super(key: key);
+ const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100});
@override
State createState() => _RenameDialogState();
diff --git a/auth/lib/ui/common/web_page.dart b/auth/lib/ui/common/web_page.dart
index 838855c7f..a714bbeb2 100644
--- a/auth/lib/ui/common/web_page.dart
+++ b/auth/lib/ui/common/web_page.dart
@@ -6,7 +6,7 @@ class WebPage extends StatefulWidget {
final String title;
final String url;
- const WebPage(this.title, this.url, {Key? key}) : super(key: key);
+ const WebPage(this.title, this.url, {super.key});
@override
State createState() => _WebPageState();
@@ -28,9 +28,9 @@ class _WebPageState extends State {
),
backgroundColor: Colors.black,
body: InAppWebView(
- initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
- initialOptions: InAppWebViewGroupOptions(
- crossPlatform: InAppWebViewOptions(transparentBackground: true),
+ initialUrlRequest: URLRequest(url: WebUri(widget.url)),
+ initialSettings: InAppWebViewSettings(
+ transparentBackground: true,
),
onLoadStop: (c, url) {
setState(() {
diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/auth/lib/ui/components/buttons/button_widget.dart
index 28d62f4a6..1932cc02b 100644
--- a/auth/lib/ui/components/buttons/button_widget.dart
+++ b/auth/lib/ui/components/buttons/button_widget.dart
@@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget {
final ValueNotifier? progressStatus;
const ButtonWidget({
- Key? key,
+ super.key,
required this.buttonType,
this.buttonSize = ButtonSize.large,
this.icon,
@@ -71,7 +71,7 @@ class ButtonWidget extends StatelessWidget {
this.shouldSurfaceExecutionStates = true,
this.progressStatus,
this.shouldShowSuccessConfirmation = false,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -155,7 +155,7 @@ class ButtonChildWidget extends StatefulWidget {
final bool shouldShowSuccessConfirmation;
const ButtonChildWidget({
- Key? key,
+ super.key,
required this.buttonStyle,
required this.buttonType,
required this.isDisabled,
@@ -168,7 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
this.labelText,
this.icon,
this.buttonAction,
- }) : super(key: key);
+ });
@override
State createState() => _ButtonChildWidgetState();
diff --git a/auth/lib/ui/components/buttons/icon_button_widget.dart b/auth/lib/ui/components/buttons/icon_button_widget.dart
index 9b0591d4c..eb5554318 100644
--- a/auth/lib/ui/components/buttons/icon_button_widget.dart
+++ b/auth/lib/ui/components/buttons/icon_button_widget.dart
@@ -17,7 +17,7 @@ class IconButtonWidget extends StatefulWidget {
final Color? pressedColor;
final Color? iconColor;
const IconButtonWidget({
- Key? key,
+ super.key,
required this.icon,
required this.iconButtonType,
this.disableGestureDetector = false,
@@ -25,7 +25,7 @@ class IconButtonWidget extends StatefulWidget {
this.defaultColor,
this.pressedColor,
this.iconColor,
- }) : super(key: key);
+ });
@override
State createState() => _IconButtonWidgetState();
diff --git a/auth/lib/ui/components/captioned_text_widget.dart b/auth/lib/ui/components/captioned_text_widget.dart
index f03c8551f..438d906a9 100644
--- a/auth/lib/ui/components/captioned_text_widget.dart
+++ b/auth/lib/ui/components/captioned_text_widget.dart
@@ -13,8 +13,8 @@ class CaptionedTextWidget extends StatelessWidget {
this.textStyle,
this.makeTextBold = false,
this.textColor,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/divider_widget.dart b/auth/lib/ui/components/divider_widget.dart
index 489d59145..657eb024f 100644
--- a/auth/lib/ui/components/divider_widget.dart
+++ b/auth/lib/ui/components/divider_widget.dart
@@ -14,12 +14,12 @@ class DividerWidget extends StatelessWidget {
final bool divColorHasBlur;
final EdgeInsets? padding;
const DividerWidget({
- Key? key,
+ super.key,
required this.dividerType,
this.bgColor = Colors.transparent,
this.divColorHasBlur = true,
this.padding,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/expandable_menu_item_widget.dart b/auth/lib/ui/components/expandable_menu_item_widget.dart
index 9628078be..0d3bce928 100644
--- a/auth/lib/ui/components/expandable_menu_item_widget.dart
+++ b/auth/lib/ui/components/expandable_menu_item_widget.dart
@@ -13,8 +13,8 @@ class ExpandableMenuItemWidget extends StatefulWidget {
required this.title,
required this.selectionOptionsWidget,
required this.leadingIcon,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() =>
diff --git a/auth/lib/ui/components/home_header_widget.dart b/auth/lib/ui/components/home_header_widget.dart
index 268084eef..0079cc7fa 100644
--- a/auth/lib/ui/components/home_header_widget.dart
+++ b/auth/lib/ui/components/home_header_widget.dart
@@ -1,13 +1,10 @@
-import 'dart:ui';
-
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/opened_settings_event.dart';
import 'package:flutter/material.dart';
class HomeHeaderWidget extends StatefulWidget {
final Widget centerWidget;
- const HomeHeaderWidget({required this.centerWidget, Key? key})
- : super(key: key);
+ const HomeHeaderWidget({required this.centerWidget, super.key});
@override
State createState() => _HomeHeaderWidgetState();
@@ -16,7 +13,7 @@ class HomeHeaderWidget extends StatefulWidget {
class _HomeHeaderWidgetState extends State {
@override
Widget build(BuildContext context) {
- final hasNotch = window.viewPadding.top > 65;
+ final hasNotch = View.of(context).viewPadding.top > 65;
return Padding(
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
child: Row(
diff --git a/auth/lib/ui/components/menu_item_child_widgets.dart b/auth/lib/ui/components/menu_item_child_widgets.dart
index 81b59bc54..1201f5e1e 100644
--- a/auth/lib/ui/components/menu_item_child_widgets.dart
+++ b/auth/lib/ui/components/menu_item_child_widgets.dart
@@ -12,7 +12,7 @@ class TrailingWidget extends StatefulWidget {
final double trailingExtraMargin;
final bool showExecutionStates;
const TrailingWidget({
- Key? key,
+ super.key,
required this.executionStateNotifier,
this.trailingIcon,
this.trailingIconColor,
@@ -20,7 +20,7 @@ class TrailingWidget extends StatefulWidget {
required this.trailingIconIsMuted,
required this.trailingExtraMargin,
required this.showExecutionStates,
- }) : super(key: key);
+ });
@override
State createState() => _TrailingWidgetState();
}
@@ -101,11 +101,11 @@ class ExpansionTrailingIcon extends StatelessWidget {
final IconData? trailingIcon;
final Color? trailingIconColor;
const ExpansionTrailingIcon({
- Key? key,
+ super.key,
required this.isExpanded,
this.trailingIcon,
this.trailingIconColor,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -138,12 +138,12 @@ class LeadingWidget extends StatelessWidget {
// leadIconSize deafult value is 20.
final double leadingIconSize;
const LeadingWidget({
- Key? key,
+ super.key,
required this.leadingIconSize,
this.leadingIcon,
this.leadingIconColor,
this.leadingIconWidget,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/menu_item_widget.dart b/auth/lib/ui/components/menu_item_widget.dart
index ef2249f2a..f281edd33 100644
--- a/auth/lib/ui/components/menu_item_widget.dart
+++ b/auth/lib/ui/components/menu_item_widget.dart
@@ -86,8 +86,8 @@ class MenuItemWidget extends StatefulWidget {
this.showOnlyLoadingState = false,
this.surfaceExecutionStates = true,
this.alwaysShowSuccessState = false,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _MenuItemWidgetState();
diff --git a/auth/lib/ui/components/menu_section_description_widget.dart b/auth/lib/ui/components/menu_section_description_widget.dart
index 6e521a536..bcbb9f2fa 100644
--- a/auth/lib/ui/components/menu_section_description_widget.dart
+++ b/auth/lib/ui/components/menu_section_description_widget.dart
@@ -3,8 +3,7 @@ import 'package:flutter/material.dart';
class MenuSectionDescriptionWidget extends StatelessWidget {
final String content;
- const MenuSectionDescriptionWidget({Key? key, required this.content})
- : super(key: key);
+ const MenuSectionDescriptionWidget({super.key, required this.content});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/notification_warning_widget.dart b/auth/lib/ui/components/notification_warning_widget.dart
index a799ffcd3..ab5e6dec1 100644
--- a/auth/lib/ui/components/notification_warning_widget.dart
+++ b/auth/lib/ui/components/notification_warning_widget.dart
@@ -21,14 +21,14 @@ class NotificationWidget extends StatelessWidget {
final NotificationType type;
const NotificationWidget({
- Key? key,
+ super.key,
required this.startIcon,
required this.actionIcon,
required this.text,
required this.onTap,
this.subText,
this.type = NotificationType.warning,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/title_bar_title_widget.dart b/auth/lib/ui/components/title_bar_title_widget.dart
index bc20fe5b1..0aa2534aa 100644
--- a/auth/lib/ui/components/title_bar_title_widget.dart
+++ b/auth/lib/ui/components/title_bar_title_widget.dart
@@ -6,11 +6,11 @@ class TitleBarTitleWidget extends StatelessWidget {
final bool isTitleH2;
final IconData? icon;
const TitleBarTitleWidget({
- Key? key,
+ super.key,
this.title,
this.isTitleH2 = false,
this.icon,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/title_bar_widget.dart b/auth/lib/ui/components/title_bar_widget.dart
index 8e22d4f65..16b532b96 100644
--- a/auth/lib/ui/components/title_bar_widget.dart
+++ b/auth/lib/ui/components/title_bar_widget.dart
@@ -14,7 +14,7 @@ class TitleBarWidget extends StatelessWidget {
final bool isOnTopOfScreen;
final Color? backgroundColor;
const TitleBarWidget({
- Key? key,
+ super.key,
this.leading,
this.title,
this.caption,
@@ -25,7 +25,7 @@ class TitleBarWidget extends StatelessWidget {
this.isFlexibleSpaceDisabled = false,
this.isOnTopOfScreen = true,
this.backgroundColor,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/toggle_switch_widget.dart b/auth/lib/ui/components/toggle_switch_widget.dart
index 8ae99f77b..3fc0d153c 100644
--- a/auth/lib/ui/components/toggle_switch_widget.dart
+++ b/auth/lib/ui/components/toggle_switch_widget.dart
@@ -13,8 +13,8 @@ class ToggleSwitchWidget extends StatefulWidget {
const ToggleSwitchWidget({
required this.value,
required this.onChanged,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _ToggleSwitchWidgetState();
diff --git a/auth/lib/ui/home/coach_mark_widget.dart b/auth/lib/ui/home/coach_mark_widget.dart
index f628fabd4..b0e15d7d2 100644
--- a/auth/lib/ui/home/coach_mark_widget.dart
+++ b/auth/lib/ui/home/coach_mark_widget.dart
@@ -21,37 +21,43 @@ class CoachMarkWidget extends StatelessWidget {
children: [
Expanded(
child: Container(
+ width: double.infinity,
color: Theme.of(context).colorScheme.background.withOpacity(0.1),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
- child: Column(
+ child: Row(
mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
- const Icon(
- Icons.swipe_left,
- size: 42,
- ),
- const SizedBox(
- height: 12,
- ),
- Text(
- l10n.swipeHint,
- style: Theme.of(context).textTheme.titleLarge,
- textAlign: TextAlign.center,
- ),
- const SizedBox(
- height: 16,
- ),
- SizedBox(
- width: 160,
- child: OutlinedButton(
- onPressed: () async {
- await PreferenceService.instance
- .setHasShownCoachMark(true);
- Bus.instance.fire(CodesUpdatedEvent());
- },
- child: Text(l10n.ok),
- ),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(
+ Icons.swipe_left,
+ size: 42,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Text(
+ l10n.swipeHint,
+ style: Theme.of(context).textTheme.titleLarge,
+ ),
+ const SizedBox(
+ height: 36,
+ ),
+ SizedBox(
+ width: 160,
+ child: OutlinedButton(
+ onPressed: () async {
+ await PreferenceService.instance
+ .setHasShownCoachMark(true);
+ Bus.instance.fire(CodesUpdatedEvent());
+ },
+ child: Text(l10n.ok),
+ ),
+ ),
+ ],
),
],
),
diff --git a/auth/lib/ui/home/home_empty_state.dart b/auth/lib/ui/home/home_empty_state.dart
index 8a185beef..0768f0491 100644
--- a/auth/lib/ui/home/home_empty_state.dart
+++ b/auth/lib/ui/home/home_empty_state.dart
@@ -3,6 +3,7 @@ import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:ente_auth/ui/settings/faq.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
class HomeEmptyStateWidget extends StatelessWidget {
@@ -10,86 +11,90 @@ class HomeEmptyStateWidget extends StatelessWidget {
final VoidCallback? onManuallySetupTap;
const HomeEmptyStateWidget({
- Key? key,
+ super.key,
required this.onScanTap,
required this.onManuallySetupTap,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return SingleChildScrollView(
child: Center(
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Column(
- children: [
- Image.asset(
- "assets/wallet-front-gradient.png",
- width: 200,
- height: 200,
- ),
- Text(
- l10n.setupFirstAccount,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- const SizedBox(height: 64),
- SizedBox(
- width: 400,
- child: OutlinedButton(
- onPressed: onScanTap,
- child: Text(l10n.importScanQrCode),
+ child: ConstrainedBox(
+ constraints: const BoxConstraints.tightFor(height: 800, width: 450),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ children: [
+ Image.asset(
+ "assets/wallet-front-gradient.png",
+ width: 200,
+ height: 200,
),
- ),
- const SizedBox(height: 18),
- SizedBox(
- width: 400,
- child: OutlinedButton(
- onPressed: onManuallySetupTap,
- child: Text(l10n.importEnterSetupKey),
- ),
- ),
- const SizedBox(height: 54),
- InkWell(
- onTap: () {
- routeToPage(context, ImportCodePage());
- },
- child: Text(
- l10n.importCodes,
+ Text(
+ l10n.setupFirstAccount,
textAlign: TextAlign.center,
- style: getEnteTextTheme(context)
- .bodyFaint
- .copyWith(decoration: TextDecoration.underline),
+ style: Theme.of(context).textTheme.headlineMedium,
),
- ),
- const SizedBox(height: 18),
- InkWell(
- onTap: () {
- showModalBottomSheet(
- backgroundColor:
- Theme.of(context).colorScheme.background,
- barrierColor: Colors.black87,
- context: context,
- builder: (context) {
- return const FAQQuestionsWidget();
- },
- );
- },
- child: Text(
- l10n.faq,
- textAlign: TextAlign.center,
- style: getEnteTextTheme(context)
- .bodyFaint
- .copyWith(decoration: TextDecoration.underline),
+ const SizedBox(height: 64),
+ if (PlatformUtil.isMobile())
+ SizedBox(
+ width: 400,
+ child: OutlinedButton(
+ onPressed: onScanTap,
+ child: Text(l10n.importScanQrCode),
+ ),
+ ),
+ const SizedBox(height: 18),
+ SizedBox(
+ width: 400,
+ child: OutlinedButton(
+ onPressed: onManuallySetupTap,
+ child: Text(l10n.importEnterSetupKey),
+ ),
),
- ),
- ],
- ),
- ],
+ const SizedBox(height: 54),
+ InkWell(
+ onTap: () {
+ routeToPage(context, const ImportCodePage());
+ },
+ child: Text(
+ l10n.importCodes,
+ textAlign: TextAlign.center,
+ style: getEnteTextTheme(context)
+ .bodyFaint
+ .copyWith(decoration: TextDecoration.underline),
+ ),
+ ),
+ const SizedBox(height: 18),
+ InkWell(
+ onTap: () {
+ showModalBottomSheet(
+ backgroundColor:
+ Theme.of(context).colorScheme.background,
+ barrierColor: Colors.black87,
+ context: context,
+ builder: (context) {
+ return const FAQQuestionsWidget();
+ },
+ );
+ },
+ child: Text(
+ l10n.faq,
+ textAlign: TextAlign.center,
+ style: getEnteTextTheme(context)
+ .bodyFaint
+ .copyWith(decoration: TextDecoration.underline),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
),
),
diff --git a/auth/lib/ui/home/speed_dial_label_widget.dart b/auth/lib/ui/home/speed_dial_label_widget.dart
index 40c945893..4889ffb6a 100644
--- a/auth/lib/ui/home/speed_dial_label_widget.dart
+++ b/auth/lib/ui/home/speed_dial_label_widget.dart
@@ -6,8 +6,8 @@ class SpeedDialLabelWidget extends StatelessWidget {
const SpeedDialLabelWidget(
this.label, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart
index 792a4c917..341e1ae69 100644
--- a/auth/lib/ui/home_page.dart
+++ b/auth/lib/ui/home_page.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
+import 'package:app_links/app_links.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/ente_theme_data.dart';
@@ -22,28 +23,32 @@ import 'package:ente_auth/ui/home/speed_dial_label_widget.dart';
import 'package:ente_auth/ui/scanner_page.dart';
import 'package:ente_auth/ui/settings_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/totp_util.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
+import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
-import 'package:uni_links/uni_links.dart';
class HomePage extends StatefulWidget {
- const HomePage({Key? key}) : super(key: key);
+ const HomePage({super.key});
@override
State createState() => _HomePageState();
}
class _HomePageState extends State {
- static final _settingsPage = SettingsPage(
+ late final _settingsPage = SettingsPage(
emailNotifier: UserService.instance.emailValueNotifier,
+ scaffoldKey: scaffoldKey,
);
bool _hasLoaded = false;
bool _isSettingsOpen = false;
final Logger _logger = Logger("HomePage");
+ final scaffoldKey = GlobalKey();
final TextEditingController _textController = TextEditingController();
bool _showSearchBox = false;
@@ -144,28 +149,24 @@ class _HomePageState extends State {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
- return WillPopScope(
- onWillPop: () async {
+ return PopScope(
+ onPopInvoked: (_) async {
if (_isSettingsOpen) {
- Navigator.pop(context);
- return false;
- }
- if (Platform.isAndroid) {
- // ignore: unawaited_futures
- MoveToBackground.moveTaskToBack();
- return false;
- } else {
- return true;
+ scaffoldKey.currentState!.closeDrawer();
+ return;
+ } else if (!Platform.isAndroid) {
+ Navigator.of(context).pop();
+ return;
}
+ await MoveToBackground.moveTaskToBack();
},
+ canPop: false,
child: Scaffold(
+ key: scaffoldKey,
drawerEnableOpenDragGesture: !Platform.isAndroid,
- drawer: ConstrainedBox(
- constraints: const BoxConstraints(maxWidth: 428),
- child: Drawer(
- width: double.infinity,
- child: _settingsPage,
- ),
+ drawer: Drawer(
+ width: 428,
+ child: _settingsPage,
),
onDrawerChanged: (isOpened) => _isSettingsOpen = isOpened,
body: SafeArea(
@@ -179,7 +180,7 @@ class _HomePageState extends State {
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: !_showSearchBox
- ? const Text('ente Auth')
+ ? const Text('Ente Auth')
: TextField(
autofocus: _searchText.isEmpty,
controller: _textController,
@@ -205,6 +206,7 @@ class _HomePageState extends State {
_showSearchBox = !_showSearchBox;
if (!_showSearchBox) {
_textController.clear();
+ _searchText = "";
} else {
_searchText = _textController.text;
}
@@ -233,10 +235,13 @@ class _HomePageState extends State {
onManuallySetupTap: _redirectToManualEntryPage,
);
} else {
- final list = ListView.builder(
+ final list = AlignedGridView.count(
+ crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 400)
+ .clamp(1, double.infinity)
+ .toInt(),
itemBuilder: ((context, index) {
try {
- return CodeWidget(_filteredCodes[index]);
+ return ClipRect(child: CodeWidget(_filteredCodes[index]));
} catch (e) {
return const Text("Failed");
}
@@ -255,7 +260,11 @@ class _HomePageState extends State {
children: [
Expanded(
child: _filteredCodes.isNotEmpty
- ? ListView.builder(
+ ? AlignedGridView.count(
+ crossAxisCount:
+ (MediaQuery.sizeOf(context).width ~/ 400)
+ .clamp(1, double.infinity)
+ .toInt(),
itemBuilder: ((context, index) {
Code? code;
try {
@@ -290,8 +299,10 @@ class _HomePageState extends State {
Future _initDeepLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
+ final appLinks = AppLinks();
try {
- final String? initialLink = await getInitialLink();
+ String? initialLink;
+ initialLink = await appLinks.getInitialAppLinkString();
// Parse the link and warn the user, if it is not correct,
// but keep in mind it could be `null`.
if (initialLink != null) {
@@ -307,14 +318,16 @@ class _HomePageState extends State {
}
// Attach a listener to the stream
- linkStream.listen(
- (String? link) {
- _handleDeeplink(context, link);
- },
- onError: (err) {
- _logger.severe(err);
- },
- );
+ if (!kIsWeb && !Platform.isLinux) {
+ appLinks.stringLinkStream.listen(
+ (link) {
+ _handleDeeplink(context, link);
+ },
+ onError: (err) {
+ _logger.severe(err);
+ },
+ );
+ }
return false;
}
@@ -343,6 +356,14 @@ class _HomePageState extends State {
}
Widget _getFab() {
+ if (PlatformUtil.isDesktop()) {
+ return FloatingActionButton(
+ onPressed: () => _redirectToManualEntryPage(),
+ child: const Icon(Icons.add),
+ elevation: 8.0,
+ shape: const CircleBorder(),
+ );
+ }
return SpeedDial(
icon: Icons.add,
activeIcon: Icons.close,
diff --git a/auth/lib/ui/linear_progress_widget.dart b/auth/lib/ui/linear_progress_widget.dart
index 6296e5d4a..a5dbb2140 100644
--- a/auth/lib/ui/linear_progress_widget.dart
+++ b/auth/lib/ui/linear_progress_widget.dart
@@ -6,8 +6,8 @@ class LinearProgressWidget extends StatelessWidget {
const LinearProgressWidget({
required this.color,
required this.fractionOfStorage,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/passkey_page.dart b/auth/lib/ui/passkey_page.dart
index 3c60af36e..8c2e54e98 100644
--- a/auth/lib/ui/passkey_page.dart
+++ b/auth/lib/ui/passkey_page.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
+import 'package:app_links/app_links.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
@@ -9,7 +10,6 @@ import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
-import 'package:uni_links/uni_links.dart';
import 'package:url_launcher/url_launcher_string.dart';
class PasskeyPage extends StatefulWidget {
@@ -17,8 +17,8 @@ class PasskeyPage extends StatefulWidget {
const PasskeyPage(
this.sessionID, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _PasskeyPageState();
@@ -77,8 +77,9 @@ class _PasskeyPageState extends State {
}
Future _initDeepLinks() async {
+ final appLinks = AppLinks();
// Attach a listener to the stream
- linkStream.listen(
+ appLinks.stringLinkStream.listen(
_handleDeeplink,
onError: (err) {
_logger.severe(err);
diff --git a/auth/lib/ui/scanner_gauth_page.dart b/auth/lib/ui/scanner_gauth_page.dart
index 785f1d3d3..41d0a762d 100644
--- a/auth/lib/ui/scanner_gauth_page.dart
+++ b/auth/lib/ui/scanner_gauth_page.dart
@@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScannerGoogleAuthPage extends StatefulWidget {
- const ScannerGoogleAuthPage({Key? key}) : super(key: key);
+ const ScannerGoogleAuthPage({super.key});
@override
State createState() => ScannerGoogleAuthPageState();
@@ -85,7 +85,7 @@ class ScannerGoogleAuthPageState extends State {
} catch (e) {
controller.dispose();
Navigator.of(context).pop();
- showToast(context, "Error " + e.toString());
+ showToast(context, "Error $e");
}
});
}
diff --git a/auth/lib/ui/scanner_page.dart b/auth/lib/ui/scanner_page.dart
index 0159f0cfa..6a7793631 100644
--- a/auth/lib/ui/scanner_page.dart
+++ b/auth/lib/ui/scanner_page.dart
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScannerPage extends StatefulWidget {
- const ScannerPage({Key? key}) : super(key: key);
+ const ScannerPage({super.key});
@override
State createState() => ScannerPageState();
diff --git a/auth/lib/ui/settings/about_section_widget.dart b/auth/lib/ui/settings/about_section_widget.dart
index 4fcd2c27c..a96e1f0ad 100644
--- a/auth/lib/ui/settings/about_section_widget.dart
+++ b/auth/lib/ui/settings/about_section_widget.dart
@@ -1,19 +1,19 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutSectionWidget extends StatelessWidget {
- const AboutSectionWidget({Key? key}) : super(key: key);
+ const AboutSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -104,8 +104,8 @@ class AboutMenuItemWidget extends StatelessWidget {
required this.title,
required this.url,
this.webPageTitle,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -117,13 +117,10 @@ class AboutMenuItemWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
- // ignore: unawaited_futures
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(webPageTitle ?? title, url);
- },
- ),
+ await PlatformUtil.openWebView(
+ context,
+ webPageTitle ?? title,
+ url,
);
},
);
diff --git a/auth/lib/ui/settings/account_section_widget.dart b/auth/lib/ui/settings/account_section_widget.dart
index 60e426b09..d51b2dd87 100644
--- a/auth/lib/ui/settings/account_section_widget.dart
+++ b/auth/lib/ui/settings/account_section_widget.dart
@@ -13,11 +13,12 @@ import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class AccountSectionWidget extends StatelessWidget {
- AccountSectionWidget({Key? key}) : super(key: key);
+ AccountSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -47,6 +48,7 @@ class AccountSectionWidget extends StatelessWidget {
context,
l10n.authToChangeYourEmail,
);
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
// ignore: unawaited_futures
showDialog(
@@ -106,7 +108,7 @@ class AccountSectionWidget extends StatelessWidget {
String recoveryKey;
try {
recoveryKey =
- Sodium.bin2hex(Configuration.instance.getRecoveryKey());
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
} catch (e) {
// ignore: unawaited_futures
showGenericErrorDialog(context: context);
diff --git a/auth/lib/ui/settings/app_update_dialog.dart b/auth/lib/ui/settings/app_update_dialog.dart
index 12049017c..176abc4b1 100644
--- a/auth/lib/ui/settings/app_update_dialog.dart
+++ b/auth/lib/ui/settings/app_update_dialog.dart
@@ -1,13 +1,14 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AppUpdateDialog extends StatefulWidget {
final LatestVersionInfo? latestVersionInfo;
- const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key);
+ const AppUpdateDialog(this.latestVersionInfo, {super.key});
@override
State createState() => _AppUpdateDialogState();
@@ -23,7 +24,7 @@ class _AppUpdateDialogState extends State {
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 4),
child: Text(
- "- " + log,
+ "- $log",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 14,
),
@@ -68,7 +69,9 @@ class _AppUpdateDialogState extends State {
),
),
onPressed: () => launchUrlString(
- widget.latestVersionInfo!.url!,
+ PlatformUtil.isDesktop()
+ ? widget.latestVersionInfo!.release!
+ : widget.latestVersionInfo!.url!,
mode: LaunchMode.externalApplication,
),
child: Text(
@@ -80,8 +83,8 @@ class _AppUpdateDialogState extends State {
);
final shouldForceUpdate =
UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo);
- return WillPopScope(
- onWillPop: () async => !shouldForceUpdate,
+ return PopScope(
+ canPop: !shouldForceUpdate,
child: AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
diff --git a/auth/lib/ui/settings/app_version_widget.dart b/auth/lib/ui/settings/app_version_widget.dart
index 3b15c6480..58c693f7c 100644
--- a/auth/lib/ui/settings/app_version_widget.dart
+++ b/auth/lib/ui/settings/app_version_widget.dart
@@ -4,8 +4,8 @@ import 'package:package_info_plus/package_info_plus.dart';
class AppVersionWidget extends StatefulWidget {
const AppVersionWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _AppVersionWidgetState();
@@ -48,7 +48,7 @@ class _AppVersionWidgetState extends State {
return Padding(
padding: const EdgeInsets.all(20),
child: Text(
- "Version: " + snapshot.data!,
+ "Version: ${snapshot.data!}",
style: Theme.of(context).textTheme.bodySmall,
),
);
diff --git a/auth/lib/ui/settings/danger_section_widget.dart b/auth/lib/ui/settings/danger_section_widget.dart
index 2e7eccc16..4f8160c38 100644
--- a/auth/lib/ui/settings/danger_section_widget.dart
+++ b/auth/lib/ui/settings/danger_section_widget.dart
@@ -11,7 +11,7 @@ import 'package:ente_auth/utils/navigation_util.dart';
import 'package:flutter/material.dart';
class DangerSectionWidget extends StatelessWidget {
- const DangerSectionWidget({Key? key}) : super(key: key);
+ const DangerSectionWidget({super.key});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/data/data_section_widget.dart b/auth/lib/ui/settings/data/data_section_widget.dart
index f9c3ed117..f32739d23 100644
--- a/auth/lib/ui/settings/data/data_section_widget.dart
+++ b/auth/lib/ui/settings/data/data_section_widget.dart
@@ -10,7 +10,9 @@ import 'package:ente_auth/utils/navigation_util.dart';
import 'package:flutter/material.dart';
class DataSectionWidget extends StatelessWidget {
- DataSectionWidget({Key? key}) : super(key: key);
+ // final _logger = Logger("AccountSectionWidget");
+
+ DataSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -35,8 +37,7 @@ class DataSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
- // ignore: unawaited_futures
- routeToPage(context, ImportCodePage());
+ await routeToPage(context, const ImportCodePage());
},
),
sectionOptionSpacing,
diff --git a/auth/lib/ui/settings/data/export_widget.dart b/auth/lib/ui/settings/data/export_widget.dart
index c9066ccce..ef438301c 100644
--- a/auth/lib/ui/settings/data/export_widget.dart
+++ b/auth/lib/ui/settings/data/export_widget.dart
@@ -9,14 +9,14 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/share_utils.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/cupertino.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:share_plus/share_plus.dart';
@@ -76,17 +76,17 @@ Future _requestForEncryptionPassword(
try {
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
String exportPlainText = await _getAuthDataForExport();
// Encrypt the key with this derived key
- final encResult = await CryptoUtil.encryptChaCha(
- utf8.encode(exportPlainText) as Uint8List,
+ final encResult = await CryptoUtil.encryptData(
+ utf8.encode(exportPlainText),
derivedKeyResult.key,
);
- final encContent = Sodium.bin2base64(encResult.encryptedData!);
- final encNonce = Sodium.bin2base64(encResult.header!);
+ final encContent = CryptoUtil.bin2base64(encResult.encryptedData!);
+ final encNonce = CryptoUtil.bin2base64(encResult.header!);
final EnteAuthExport data = EnteAuthExport(
version: 1,
encryptedData: encContent,
@@ -94,7 +94,7 @@ Future _requestForEncryptionPassword(
kdfParams: KDFParams(
memLimit: derivedKeyResult.memLimit,
opsLimit: derivedKeyResult.opsLimit,
- salt: Sodium.bin2base64(kekSalt),
+ salt: CryptoUtil.bin2base64(kekSalt),
),
);
// get json value of data
@@ -126,46 +126,55 @@ Future _showExportWarningDialog(BuildContext context) async {
Future _exportCodes(BuildContext context, String fileContent) async {
DateTime now = DateTime.now().toUtc();
String formattedDate = DateFormat('yyyy-MM-dd').format(now);
- String exportFileName = 'ente-auth-codes-$formattedDate.txt';
- final _codeFile = File(
- Configuration.instance.getTempDirectory() + exportFileName,
- );
+ String exportFileName = 'ente-auth-codes-$formattedDate';
+ String exportFileExtension = 'txt';
final hasAuthenticated = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.authToExportCodes);
+ await PlatformUtil.refocusWindows();
if (!hasAuthenticated) {
return;
}
- if (_codeFile.existsSync()) {
- await _codeFile.delete();
- }
- _codeFile.writeAsStringSync(fileContent);
- final Size size = MediaQuery.of(context).size;
-
- if (Platform.isAndroid) {
- await FileSaver.instance.saveAs(
- name: exportFileName,
- filePath: _codeFile.path,
- mimeType: MimeType.text,
- ext: 'txt',
- );
- } else {
- await Share.shareFiles(
- [_codeFile.path],
- sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
- );
- }
- Future.delayed(const Duration(seconds: 30), () async {
- if (_codeFile.existsSync()) {
- _codeFile.deleteSync();
- }
- });
+ Future.delayed(
+ const Duration(milliseconds: 1200),
+ () async => await shareDialog(
+ context,
+ context.l10n.exportCodes,
+ saveAction: () async {
+ await PlatformUtil.shareFile(
+ exportFileName,
+ exportFileExtension,
+ CryptoUtil.strToBin(fileContent),
+ MimeType.text,
+ );
+ },
+ sendAction: () async {
+ final codeFile = File(
+ "${Configuration.instance.getTempDirectory()}$exportFileName.$exportFileExtension",
+ );
+ if (codeFile.existsSync()) {
+ await codeFile.delete();
+ }
+ codeFile.writeAsStringSync(fileContent);
+ final Size size = MediaQuery.of(context).size;
+ await Share.shareXFiles(
+ [XFile(codeFile.path)],
+ sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
+ );
+ Future.delayed(const Duration(seconds: 30), () async {
+ if (codeFile.existsSync()) {
+ codeFile.deleteSync();
+ }
+ });
+ },
+ ),
+ );
}
Future _getAuthDataForExport() async {
final codes = await CodeStore.instance.getAllCodes();
String data = "";
for (final code in codes) {
- data += code.rawData + "\n";
+ data += "${code.rawData}\n";
}
return data;
}
diff --git a/auth/lib/ui/settings/data/import/aegis_import.dart b/auth/lib/ui/settings/data/import/aegis_import.dart
index 5042d618f..b801e64a5 100644
--- a/auth/lib/ui/settings/data/import/aegis_import.dart
+++ b/auth/lib/ui/settings/data/import/aegis_import.dart
@@ -91,7 +91,7 @@ Future _processAegisExportFile(
final jsonString = await file.readAsString();
final decodedJson = jsonDecode(jsonString);
final isEncrypted = decodedJson['header']['slots'] != null;
- var aegisDB;
+ Map? aegisDB;
if (isEncrypted) {
String? password;
try {
@@ -127,7 +127,7 @@ Future _processAegisExportFile(
aegisDB = decodedJson['db'];
}
final parsedCodes = [];
- for (var item in aegisDB['entries']) {
+ for (var item in aegisDB?['entries']) {
var kind = item['type'];
var account = item['name'];
var issuer = item['issuer'];
diff --git a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
index 15f869206..511c9bbf9 100644
--- a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
+++ b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
@@ -11,21 +11,20 @@ import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
Future showEncryptedImportInstruction(BuildContext context) async {
final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
- title: l10n.importFromApp("ente Auth"),
+ title: l10n.importFromApp("Ente Auth"),
body: l10n.importEnteEncGuide,
buttons: [
ButtonWidget(
@@ -80,21 +79,21 @@ Future _decryptExportData(
try {
await progressDialog.show();
final derivedKey = await CryptoUtil.deriveKey(
- utf8.encode(password) as Uint8List,
- Sodium.base642bin(enteAuthExport.kdfParams.salt),
+ utf8.encode(password),
+ CryptoUtil.base642bin(enteAuthExport.kdfParams.salt),
enteAuthExport.kdfParams.memLimit,
enteAuthExport.kdfParams.opsLimit,
);
Uint8List? decryptedContent;
// Encrypt the key with this derived key
try {
- decryptedContent = await CryptoUtil.decryptChaCha(
- Sodium.base642bin(enteAuthExport.encryptedData),
+ decryptedContent = await CryptoUtil.decryptData(
+ CryptoUtil.base642bin(enteAuthExport.encryptedData),
derivedKey,
- Sodium.base642bin(enteAuthExport.encryptionNonce),
+ CryptoUtil.base642bin(enteAuthExport.encryptionNonce),
);
- } catch (e,s) {
- Logger("encryptedImport").warning('failed to decrypt',e,s);
+ } catch (e, s) {
+ Logger("encryptedImport").warning('failed to decrypt', e, s);
showToast(context, l10n.incorrectPasswordTitle);
isPasswordIncorrect = true;
}
diff --git a/auth/lib/ui/settings/data/import/google_auth_import.dart b/auth/lib/ui/settings/data/import/google_auth_import.dart
index 2c83bc206..12df41a14 100644
--- a/auth/lib/ui/settings/data/import/google_auth_import.dart
+++ b/auth/lib/ui/settings/data/import/google_auth_import.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:typed_data';
import 'package:base32/base32.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
@@ -12,6 +11,8 @@ import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/scanner_gauth_page.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -24,13 +25,14 @@ Future showGoogleAuthInstruction(BuildContext context) async {
title: l10n.importFromApp("Google Authenticator"),
body: l10n.importGoogleAuthGuide,
buttons: [
- ButtonWidget(
- buttonType: ButtonType.primary,
- labelText: l10n.scanAQrCode,
- isInAlert: true,
- buttonSize: ButtonSize.large,
- buttonAction: ButtonAction.first,
- ),
+ if (PlatformUtil.isMobile())
+ ButtonWidget(
+ buttonType: ButtonType.primary,
+ labelText: l10n.scanAQrCode,
+ isInAlert: true,
+ buttonSize: ButtonSize.large,
+ buttonAction: ButtonAction.first,
+ ),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.cancel,
diff --git a/auth/lib/ui/settings/data/import/import_service.dart b/auth/lib/ui/settings/data/import/import_service.dart
index 0e42197ba..a315c1679 100644
--- a/auth/lib/ui/settings/data/import/import_service.dart
+++ b/auth/lib/ui/settings/data/import/import_service.dart
@@ -1,4 +1,3 @@
-import 'package:ente_auth/ui/settings/data/import/2fas_import.dart';
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
@@ -6,6 +5,7 @@ import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
+import 'package:ente_auth/ui/settings/data/import/two_fas_import.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:flutter/cupertino.dart';
diff --git a/auth/lib/ui/settings/data/import/import_success.dart b/auth/lib/ui/settings/data/import/import_success.dart
index cc77248e8..5827248f8 100644
--- a/auth/lib/ui/settings/data/import/import_success.dart
+++ b/auth/lib/ui/settings/data/import/import_success.dart
@@ -11,9 +11,9 @@ Future importSuccessDialog(BuildContext context, int count) async {
firstButtonLabel: context.l10n.ok,
firstButtonOnTap: () async {
Navigator.of(context).pop();
- if(Navigator.of(context).canPop()) {
- Navigator.of(context).pop();
- }
+ // if(Navigator.of(context).canPop()) {
+ // Navigator.of(context).pop();
+ // }
},
firstButtonType: ButtonType.primary,
);
diff --git a/auth/lib/ui/settings/data/import/plain_text_import.dart b/auth/lib/ui/settings/data/import/plain_text_import.dart
index a8e64bb5f..03bc50dce 100644
--- a/auth/lib/ui/settings/data/import/plain_text_import.dart
+++ b/auth/lib/ui/settings/data/import/plain_text_import.dart
@@ -1,6 +1,6 @@
import 'dart:async';
+import 'dart:convert';
import 'dart:io';
-import 'dart:ui';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
@@ -48,10 +48,8 @@ class PlainTextImport extends StatelessWidget {
],
);
}
-
}
-
Future showImportInstructionDialog(BuildContext context) async {
final l10n = context.l10n;
final AlertDialog alert = AlertDialog(
@@ -94,7 +92,6 @@ Future showImportInstructionDialog(BuildContext context) async {
);
}
-
Future _pickImportFile(BuildContext context) async {
final l10n = context.l10n;
FilePickerResult? result = await FilePicker.platform.pickFiles();
@@ -108,7 +105,7 @@ Future _pickImportFile(BuildContext context) async {
final codes = await file.readAsString();
List splitCodes = codes.split(",");
if (splitCodes.length == 1) {
- splitCodes = codes.split("\n");
+ splitCodes = const LineSplitter().convert(codes);
}
final parsedCodes = [];
for (final code in splitCodes) {
diff --git a/auth/lib/ui/settings/data/import/2fas_import.dart b/auth/lib/ui/settings/data/import/two_fas_import.dart
similarity index 98%
rename from auth/lib/ui/settings/data/import/2fas_import.dart
rename to auth/lib/ui/settings/data/import/two_fas_import.dart
index b6e75f3c6..ae5a05b0b 100644
--- a/auth/lib/ui/settings/data/import/2fas_import.dart
+++ b/auth/lib/ui/settings/data/import/two_fas_import.dart
@@ -170,8 +170,8 @@ Future _process2FasExportFile(
}
String decrypt2FasVault(dynamic data, {required String password}) {
- int ITERATION_COUNT = 10000;
- int KEY_SIZE = 256;
+ int iterationCount = 10000;
+ int keySize = 256;
final String encryptedServices = data["servicesEncrypted"];
var split = encryptedServices.split(":");
final encryptedData = base64.decode(split[0]);
@@ -181,11 +181,11 @@ String decrypt2FasVault(dynamic data, {required String password}) {
final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
final params = Pbkdf2Parameters(
salt,
- ITERATION_COUNT,
- KEY_SIZE ~/ 8,
+ iterationCount,
+ keySize ~/ 8,
);
pbkdf2.init(params);
- Uint8List key = Uint8List(KEY_SIZE ~/ 8);
+ Uint8List key = Uint8List(keySize ~/ 8);
pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
final decrypted = decrypt(key, iv, encryptedData);
final utf8Decode = utf8.decode(decrypted);
diff --git a/auth/lib/ui/settings/data/import_page.dart b/auth/lib/ui/settings/data/import_page.dart
index cd0f2114a..c5ad2206e 100644
--- a/auth/lib/ui/settings/data/import_page.dart
+++ b/auth/lib/ui/settings/data/import_page.dart
@@ -21,7 +21,9 @@ enum ImportType {
}
class ImportCodePage extends StatelessWidget {
- final List importOptions = [
+ const ImportCodePage({super.key});
+
+ static const List importOptions = [
ImportType.plainText,
ImportType.encrypted,
ImportType.twoFas,
@@ -32,8 +34,6 @@ class ImportCodePage extends StatelessWidget {
ImportType.lastpass,
];
- ImportCodePage({super.key});
-
String getTitle(BuildContext context, ImportType type) {
switch (type) {
case ImportType.plainText:
diff --git a/auth/lib/ui/settings/debug_section_widget.dart b/auth/lib/ui/settings/debug_section_widget.dart
index 259937ff7..03406f791 100644
--- a/auth/lib/ui/settings/debug_section_widget.dart
+++ b/auth/lib/ui/settings/debug_section_widget.dart
@@ -3,9 +3,9 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/settings_section_title.dart';
import 'package:ente_auth/ui/settings/settings_text_item.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class DebugSectionWidget extends StatelessWidget {
const DebugSectionWidget({super.key});
@@ -51,7 +51,7 @@ class DebugSectionWidget extends StatelessWidget {
"Key",
style: TextStyle(fontWeight: FontWeight.bold),
),
- Text(Sodium.bin2base64(Configuration.instance.getKey()!)),
+ Text(CryptoUtil.bin2base64(Configuration.instance.getKey()!)),
const Padding(padding: EdgeInsets.all(12)),
const Text(
"Encrypted Key",
diff --git a/auth/lib/ui/settings/developer_settings_page.dart b/auth/lib/ui/settings/developer_settings_page.dart
index 96139e982..5bd72be40 100644
--- a/auth/lib/ui/settings/developer_settings_page.dart
+++ b/auth/lib/ui/settings/developer_settings_page.dart
@@ -11,7 +11,7 @@ class DeveloperSettingsPage extends StatefulWidget {
const DeveloperSettingsPage({super.key});
@override
- _DeveloperSettingsPageState createState() => _DeveloperSettingsPageState();
+ State createState() => _DeveloperSettingsPageState();
}
class _DeveloperSettingsPageState extends State {
@@ -27,7 +27,7 @@ class _DeveloperSettingsPageState extends State {
@override
Widget build(BuildContext context) {
_logger.info(
- "Current endpoint is: " + Configuration.instance.getHttpEndpoint(),
+ "Current endpoint is: ${Configuration.instance.getHttpEndpoint()}",
);
return Scaffold(
appBar: AppBar(
@@ -49,7 +49,7 @@ class _DeveloperSettingsPageState extends State {
GradientButton(
onTap: () async {
String url = _urlController.text;
- _logger.info("Entered endpoint: " + url);
+ _logger.info("Entered endpoint: $url");
try {
final uri = Uri.parse(url);
if ((uri.scheme == "http" || uri.scheme == "https")) {
@@ -79,7 +79,7 @@ class _DeveloperSettingsPageState extends State {
Future _ping(String endpoint) async {
try {
- final response = await Dio().get(endpoint + '/ping');
+ final response = await Dio().get('$endpoint/ping');
if (response.data['message'] != 'pong') {
throw Exception('Invalid response');
}
diff --git a/auth/lib/ui/settings/developer_settings_widget.dart b/auth/lib/ui/settings/developer_settings_widget.dart
index 0fb32301c..32f05f929 100644
--- a/auth/lib/ui/settings/developer_settings_widget.dart
+++ b/auth/lib/ui/settings/developer_settings_widget.dart
@@ -15,7 +15,7 @@ class DeveloperSettingsWidget extends StatelessWidget {
padding: const EdgeInsets.only(bottom: 20),
child: Text(
context.l10n.customEndpoint(
- endpointURI.host + ":" + endpointURI.port.toString(),
+ "${endpointURI.host}:${endpointURI.port}",
),
style: Theme.of(context).textTheme.bodySmall,
),
diff --git a/auth/lib/ui/settings/faq.dart b/auth/lib/ui/settings/faq.dart
index d9aa88f1b..5f441912e 100644
--- a/auth/lib/ui/settings/faq.dart
+++ b/auth/lib/ui/settings/faq.dart
@@ -8,8 +8,8 @@ import 'package:flutter/material.dart';
class FAQQuestionsWidget extends StatelessWidget {
const FAQQuestionsWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -64,9 +64,9 @@ class FAQQuestionsWidget extends StatelessWidget {
class FaqWidget extends StatelessWidget {
const FaqWidget({
- Key? key,
+ super.key,
required this.faq,
- }) : super(key: key);
+ });
final FaqItem? faq;
diff --git a/auth/lib/ui/settings/general_section_widget.dart b/auth/lib/ui/settings/general_section_widget.dart
index 4518be8de..2b74cbf80 100644
--- a/auth/lib/ui/settings/general_section_widget.dart
+++ b/auth/lib/ui/settings/general_section_widget.dart
@@ -17,7 +17,7 @@ import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class AdvancedSectionWidget extends StatefulWidget {
- const AdvancedSectionWidget({Key? key}) : super(key: key);
+ const AdvancedSectionWidget({super.key});
@override
State createState() => _AdvancedSectionWidgetState();
diff --git a/auth/lib/ui/settings/language_picker.dart b/auth/lib/ui/settings/language_picker.dart
index 997e53068..1d0aa32e8 100644
--- a/auth/lib/ui/settings/language_picker.dart
+++ b/auth/lib/ui/settings/language_picker.dart
@@ -18,53 +18,58 @@ class LanguageSelectorPage extends StatelessWidget {
this.supportedLocales,
this.onLocaleChanged,
this.currentLocale, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
- body: CustomScrollView(
- primary: false,
- slivers: [
- TitleBarWidget(
- flexibleSpaceTitle: TitleBarTitleWidget(
- title: l10n.selectLanguage,
- ),
- ),
- SliverList(
- delegate: SliverChildBuilderDelegate(
- (context, index) {
- return Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 20,
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- ClipRRect(
- borderRadius:
- const BorderRadius.all(Radius.circular(8)),
- child: ItemsWidget(
- supportedLocales,
- onLocaleChanged,
- currentLocale,
- ),
+ body: Center(
+ child: Container(
+ constraints: const BoxConstraints.tightFor(width: 450),
+ child: CustomScrollView(
+ primary: false,
+ slivers: [
+ TitleBarWidget(
+ flexibleSpaceTitle: TitleBarTitleWidget(
+ title: l10n.selectLanguage,
+ ),
+ ),
+ SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, index) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ vertical: 20,
),
- // MenuSectionDescriptionWidget(
- // content: context.l10n.maxDeviceLimitSpikeHandling(50),
- // )
- ],
- ),
- );
- },
- childCount: 1,
- ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ ClipRRect(
+ borderRadius:
+ const BorderRadius.all(Radius.circular(8)),
+ child: ItemsWidget(
+ supportedLocales,
+ onLocaleChanged,
+ currentLocale,
+ ),
+ ),
+ // MenuSectionDescriptionWidget(
+ // content: context.l10n.maxDeviceLimitSpikeHandling(50),
+ // )
+ ],
+ ),
+ );
+ },
+ childCount: 1,
+ ),
+ ),
+ const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)),
+ ],
),
- const SliverPadding(padding: EdgeInsets.symmetric(vertical: 12)),
- ],
+ ),
),
);
}
@@ -79,8 +84,8 @@ class ItemsWidget extends StatefulWidget {
this.supportedLocales,
this.onLocaleChanged,
this.currentLocale, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _ItemsWidgetState();
diff --git a/auth/lib/ui/settings/made_with_love_widget.dart b/auth/lib/ui/settings/made_with_love_widget.dart
index ff1715efd..a8e12c106 100644
--- a/auth/lib/ui/settings/made_with_love_widget.dart
+++ b/auth/lib/ui/settings/made_with_love_widget.dart
@@ -4,8 +4,8 @@ import 'package:url_launcher/url_launcher.dart';
class MadeWithLoveWidget extends StatelessWidget {
const MadeWithLoveWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/security_section_widget.dart b/auth/lib/ui/settings/security_section_widget.dart
index f4cc3324f..4e50d38fb 100644
--- a/auth/lib/ui/settings/security_section_widget.dart
+++ b/auth/lib/ui/settings/security_section_widget.dart
@@ -16,15 +16,16 @@ import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class SecuritySectionWidget extends StatefulWidget {
- const SecuritySectionWidget({Key? key}) : super(key: key);
+ const SecuritySectionWidget({super.key});
@override
State createState() => _SecuritySectionWidgetState();
@@ -94,6 +95,7 @@ class _SecuritySectionWidgetState extends State {
);
final isEmailMFAEnabled =
UserService.instance.hasEmailMFAEnabled();
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
await updateEmailMFA(!isEmailMFAEnabled);
if (mounted) {
@@ -117,6 +119,7 @@ class _SecuritySectionWidgetState extends State {
context,
context.l10n.authToViewYourActiveSessions,
);
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
// ignore: unawaited_futures
Navigator.of(context).push(
@@ -149,6 +152,7 @@ class _SecuritySectionWidgetState extends State {
context.l10n.lockScreenEnablePreSteps,
);
if (hasAuthenticated) {
+ FocusScope.of(context).requestFocus();
setState(() {});
}
},
diff --git a/auth/lib/ui/settings/settings_section_title.dart b/auth/lib/ui/settings/settings_section_title.dart
index 08dbc69ec..8b7e549cf 100644
--- a/auth/lib/ui/settings/settings_section_title.dart
+++ b/auth/lib/ui/settings/settings_section_title.dart
@@ -8,9 +8,9 @@ class SettingsSectionTitle extends StatelessWidget {
const SettingsSectionTitle(
this.title, {
- Key? key,
+ super.key,
this.color,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/settings_text_item.dart b/auth/lib/ui/settings/settings_text_item.dart
index 6273c7cad..afaf56432 100644
--- a/auth/lib/ui/settings/settings_text_item.dart
+++ b/auth/lib/ui/settings/settings_text_item.dart
@@ -8,10 +8,10 @@ class SettingsTextItem extends StatelessWidget {
final String text;
final IconData icon;
const SettingsTextItem({
- Key? key,
+ super.key,
required this.text,
required this.icon,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/social_section_widget.dart b/auth/lib/ui/settings/social_section_widget.dart
index 62b200453..cda18366c 100644
--- a/auth/lib/ui/settings/social_section_widget.dart
+++ b/auth/lib/ui/settings/social_section_widget.dart
@@ -7,11 +7,12 @@ import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SocialSectionWidget extends StatelessWidget {
- const SocialSectionWidget({Key? key}) : super(key: key);
+ const SocialSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -31,8 +32,10 @@ class SocialSectionWidget extends StatelessWidget {
final List options = [
sectionOptionSpacing,
- SocialsMenuItemWidget(l10n.rateUsOnStore(ratePlace), rateUrl),
- sectionOptionSpacing,
+ if (PlatformUtil.isMobile()) ...[
+ SocialsMenuItemWidget(l10n.rateUsOnStore(ratePlace), rateUrl),
+ sectionOptionSpacing,
+ ],
SocialsMenuItemWidget(
l10n.blog,
"https://ente.io/blog",
@@ -65,11 +68,11 @@ class SocialsMenuItemWidget extends StatelessWidget {
final bool launchInExternalApp;
const SocialsMenuItemWidget(
- this.text,
- this.url, {
- Key? key,
- this.launchInExternalApp = true,
- }) : super(key: key);
+ this.text,
+ this.url, {
+ super.key,
+ this.launchInExternalApp = true,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/support_dev_widget.dart b/auth/lib/ui/settings/support_dev_widget.dart
index 63fa92c27..849b95415 100644
--- a/auth/lib/ui/settings/support_dev_widget.dart
+++ b/auth/lib/ui/settings/support_dev_widget.dart
@@ -10,8 +10,8 @@ import 'package:url_launcher/url_launcher.dart';
class SupportDevWidget extends StatelessWidget {
const SupportDevWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/support_section_widget.dart b/auth/lib/ui/settings/support_section_widget.dart
index 418c1d430..1343d2347 100644
--- a/auth/lib/ui/settings/support_section_widget.dart
+++ b/auth/lib/ui/settings/support_section_widget.dart
@@ -12,7 +12,7 @@ import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SupportSectionWidget extends StatefulWidget {
- const SupportSectionWidget({Key? key}) : super(key: key);
+ const SupportSectionWidget({super.key});
@override
State createState() => _SupportSectionWidgetState();
@@ -64,7 +64,7 @@ class _SupportSectionWidgetState extends State {
onTap: () async {
// ignore: unawaited_futures
launchUrlString(
- githubDiscussionsUrl,
+ githubIssuesUrl,
mode: LaunchMode.externalApplication,
);
},
diff --git a/auth/lib/ui/settings/theme_switch_widget.dart b/auth/lib/ui/settings/theme_switch_widget.dart
index 69e4513f1..c0495b65b 100644
--- a/auth/lib/ui/settings/theme_switch_widget.dart
+++ b/auth/lib/ui/settings/theme_switch_widget.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ThemeSwitchWidget extends StatefulWidget {
- const ThemeSwitchWidget({Key? key}) : super(key: key);
+ const ThemeSwitchWidget({super.key});
@override
State createState() => _ThemeSwitchWidgetState();
diff --git a/auth/lib/ui/settings/title_bar_widget.dart b/auth/lib/ui/settings/title_bar_widget.dart
index e11a5c287..8d9aff85c 100644
--- a/auth/lib/ui/settings/title_bar_widget.dart
+++ b/auth/lib/ui/settings/title_bar_widget.dart
@@ -2,7 +2,12 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/material.dart';
class SettingsTitleBarWidget extends StatelessWidget {
- const SettingsTitleBarWidget({Key? key}) : super(key: key);
+ const SettingsTitleBarWidget({
+ super.key,
+ required this.scaffoldKey,
+ });
+
+ final GlobalKey scaffoldKey;
@override
Widget build(BuildContext context) {
@@ -17,7 +22,7 @@ class SettingsTitleBarWidget extends StatelessWidget {
IconButton(
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
onPressed: () {
- Navigator.pop(context);
+ scaffoldKey.currentState?.closeDrawer();
},
icon: const Icon(Icons.keyboard_double_arrow_left_outlined),
),
diff --git a/auth/lib/ui/settings_page.dart b/auth/lib/ui/settings_page.dart
index cfe5ba874..48fd6467c 100644
--- a/auth/lib/ui/settings_page.dart
+++ b/auth/lib/ui/settings_page.dart
@@ -26,18 +26,24 @@ import 'package:ente_auth/ui/settings/theme_switch_widget.dart';
import 'package:ente_auth/ui/settings/title_bar_widget.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
final ValueNotifier emailNotifier;
+ final GlobalKey scaffoldKey;
- SettingsPage({Key? key, required this.emailNotifier}) : super(key: key);
+ SettingsPage({
+ super.key,
+ required this.emailNotifier,
+ required this.scaffoldKey,
+ });
@override
Widget build(BuildContext context) {
- final _hasLoggedIn = Configuration.instance.hasConfiguredAccount();
- if (_hasLoggedIn) {
+ final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
+ if (hasLoggedIn) {
UserService.instance.getUserDetailsV2().ignore();
}
final enteColorScheme = getEnteColorScheme(context);
@@ -50,11 +56,11 @@ class SettingsPage extends StatelessWidget {
}
Widget _getBody(BuildContext context, EnteColorScheme colorScheme) {
- final _hasLoggedIn = Configuration.instance.hasConfiguredAccount();
+ final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
final enteTextTheme = getEnteTextTheme(context);
const sectionSpacing = SizedBox(height: 8);
final List contents = [];
- if (_hasLoggedIn) {
+ if (hasLoggedIn) {
contents.add(
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
@@ -111,6 +117,7 @@ class SettingsPage extends StatelessWidget {
context,
context.l10n.authToInitiateSignIn,
);
+ await PlatformUtil.refocusWindows();
if (!hasAuthenticated) {
return;
}
@@ -163,7 +170,9 @@ class SettingsPage extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const SettingsTitleBarWidget(),
+ SettingsTitleBarWidget(
+ scaffoldKey: scaffoldKey,
+ ),
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Column(
diff --git a/auth/lib/ui/tools/app_lock.dart b/auth/lib/ui/tools/app_lock.dart
index 2e7e0f54b..df55f8116 100644
--- a/auth/lib/ui/tools/app_lock.dart
+++ b/auth/lib/ui/tools/app_lock.dart
@@ -37,7 +37,7 @@ class AppLock extends StatefulWidget {
final Locale locale;
const AppLock({
- Key? key,
+ super.key,
required this.builder,
required this.lockScreen,
required this.savedThemeMode,
@@ -46,7 +46,7 @@ class AppLock extends StatefulWidget {
this.backgroundLockLatency = const Duration(seconds: 0),
this.darkTheme,
this.lightTheme,
- }) : super(key: key);
+ });
static _AppLockState? of(BuildContext context) =>
context.findAncestorStateOfType<_AppLockState>();
@@ -135,9 +135,9 @@ class _AppLockState extends State with WidgetsBindingObserver {
}
Widget get _lockScreen {
- return WillPopScope(
+ return PopScope(
child: this.widget.lockScreen,
- onWillPop: () => Future.value(false),
+ canPop: false,
);
}
diff --git a/auth/lib/ui/tools/debug/log_file_viewer.dart b/auth/lib/ui/tools/debug/log_file_viewer.dart
index cc9321aa4..bcee33bc2 100644
--- a/auth/lib/ui/tools/debug/log_file_viewer.dart
+++ b/auth/lib/ui/tools/debug/log_file_viewer.dart
@@ -1,12 +1,12 @@
import 'dart:io';
-import 'dart:ui';
import 'package:ente_auth/ui/common/loading_widget.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
class LogFileViewer extends StatefulWidget {
final File file;
- const LogFileViewer(this.file, {Key? key}) : super(key: key);
+ const LogFileViewer(this.file, {super.key});
@override
State createState() => _LogFileViewerState();
@@ -42,13 +42,17 @@ class _LogFileViewerState extends State {
return Container(
padding: const EdgeInsets.only(left: 12, top: 8, right: 12),
child: SingleChildScrollView(
- child: Text(
- _logs!,
- style: const TextStyle(
- fontFeatures: [
- FontFeature.tabularFigures(),
- ],
- height: 1.2,
+ child: SelectableRegion(
+ focusNode: FocusNode(),
+ selectionControls: PlatformUtil.selectionControls,
+ child: Text(
+ _logs!,
+ style: const TextStyle(
+ fontFeatures: [
+ FontFeature.tabularFigures(),
+ ],
+ height: 1.2,
+ ),
),
),
),
diff --git a/auth/lib/ui/tools/lock_screen.dart b/auth/lib/ui/tools/lock_screen.dart
index c2e725f1e..b6e2126e1 100644
--- a/auth/lib/ui/tools/lock_screen.dart
+++ b/auth/lib/ui/tools/lock_screen.dart
@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class LockScreen extends StatefulWidget {
- const LockScreen({Key? key}) : super(key: key);
+ const LockScreen({super.key});
@override
State createState() => _LockScreenState();
diff --git a/auth/lib/ui/two_factor_authentication_page.dart b/auth/lib/ui/two_factor_authentication_page.dart
index 43b8f967e..58a462286 100644
--- a/auth/lib/ui/two_factor_authentication_page.dart
+++ b/auth/lib/ui/two_factor_authentication_page.dart
@@ -4,13 +4,13 @@ import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:pinput/pin_put/pin_put.dart';
+
+import 'package:pinput/pinput.dart';
class TwoFactorAuthenticationPage extends StatefulWidget {
final String sessionID;
- const TwoFactorAuthenticationPage(this.sessionID, {Key? key})
- : super(key: key);
+ const TwoFactorAuthenticationPage(this.sessionID, {super.key});
@override
State createState() =>
@@ -86,29 +86,31 @@ class _TwoFactorAuthenticationPageState
const Padding(padding: EdgeInsets.all(32)),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
- child: PinPut(
- fieldsCount: 6,
- onSubmit: (String code) {
+ child: Pinput(
+ onSubmitted: (String code) {
_verifyTwoFactorCode(code);
},
+ length: 6,
+ defaultPinTheme: const PinTheme(),
+ submittedPinTheme: PinTheme(
+ decoration: pinPutDecoration.copyWith(
+ borderRadius: BorderRadius.circular(20.0),
+ ),
+ ),
+ focusedPinTheme: PinTheme(
+ decoration: pinPutDecoration,
+ ),
+ followingPinTheme: PinTheme(
+ decoration: pinPutDecoration.copyWith(
+ borderRadius: BorderRadius.circular(5.0),
+ ),
+ ),
onChanged: (String pin) {
setState(() {
_code = pin;
});
},
controller: _pinController,
- submittedFieldDecoration: pinPutDecoration.copyWith(
- borderRadius: BorderRadius.circular(20.0),
- ),
- selectedFieldDecoration: pinPutDecoration,
- followingFieldDecoration: pinPutDecoration.copyWith(
- borderRadius: BorderRadius.circular(5.0),
- ),
- inputDecoration: const InputDecoration(
- focusedBorder: InputBorder.none,
- border: InputBorder.none,
- counterText: '',
- ),
autofocus: true,
),
),
diff --git a/auth/lib/ui/two_factor_recovery_page.dart b/auth/lib/ui/two_factor_recovery_page.dart
index 4eae7ff2d..5743f0246 100644
--- a/auth/lib/ui/two_factor_recovery_page.dart
+++ b/auth/lib/ui/two_factor_recovery_page.dart
@@ -1,5 +1,3 @@
-import 'dart:ui';
-
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/account/two_factor.dart';
import 'package:ente_auth/services/user_service.dart';
@@ -17,8 +15,8 @@ class TwoFactorRecoveryPage extends StatefulWidget {
this.sessionID,
this.encryptedSecret,
this.secretDecryptionNonce, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _TwoFactorRecoveryPageState();
diff --git a/auth/lib/ui/utils/icon_utils.dart b/auth/lib/ui/utils/icon_utils.dart
index 298a14333..771303952 100644
--- a/auth/lib/ui/utils/icon_utils.dart
+++ b/auth/lib/ui/utils/icon_utils.dart
@@ -87,7 +87,7 @@ class IconUtils {
Color? _getAdaptiveColor(String? hexColor, BuildContext context) {
if (hexColor == null) return null;
final theme = Theme.of(context).brightness;
- final color = Color(int.parse("0xFF" + hexColor));
+ final color = Color(int.parse("0xFF$hexColor"));
// Color is close to neutral-grey and it's too light or dark for theme
if (_isCloseToNeutralGrey(color) &&
((theme == Brightness.light && _getColorLuminance(color) > 0.70) ||
diff --git a/auth/lib/utils/auth_util.dart b/auth/lib/utils/auth_util.dart
index 0a6320191..c2d2f5afa 100644
--- a/auth/lib/utils/auth_util.dart
+++ b/auth/lib/utils/auth_util.dart
@@ -1,38 +1,45 @@
+import 'dart:io';
+
import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter_local_authentication/flutter_local_authentication.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth_android/local_auth_android.dart';
-import 'package:local_auth_ios/types/auth_messages_ios.dart';
+import 'package:local_auth_darwin/types/auth_messages_ios.dart';
import 'package:logging/logging.dart';
Future requestAuthentication(BuildContext context, String reason) async {
Logger("AuthUtil").info("Requesting authentication");
- await LocalAuthentication().stopAuthentication();
- final l10n = context.l10n;
- return await LocalAuthentication().authenticate(
- localizedReason: reason,
- authMessages: [
- AndroidAuthMessages(
- biometricHint: l10n.androidBiometricHint,
- biometricNotRecognized: l10n.androidBiometricNotRecognized,
- biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
- biometricSuccess: l10n.androidBiometricSuccess,
- cancelButton: l10n.androidCancelButton,
- deviceCredentialsRequiredTitle:
- l10n.androidDeviceCredentialsRequiredTitle,
- deviceCredentialsSetupDescription:
- l10n.androidDeviceCredentialsSetupDescription,
- goToSettingsButton: l10n.goToSettings,
- goToSettingsDescription: l10n.androidGoToSettingsDescription,
- signInTitle: l10n.androidSignInTitle,
- ),
- IOSAuthMessages(
- goToSettingsButton: l10n.goToSettings,
- goToSettingsDescription: l10n.goToSettings,
- lockOut: l10n.iOSLockOut,
- // cancelButton default value is "Ok"
- cancelButton: l10n.iOSOkButton,
- ),
- ],
- );
+ if (Platform.isMacOS || Platform.isLinux) {
+ return await FlutterLocalAuthentication().authenticate();
+ } else {
+ await LocalAuthentication().stopAuthentication();
+ final l10n = context.l10n;
+ return await LocalAuthentication().authenticate(
+ localizedReason: reason,
+ authMessages: [
+ AndroidAuthMessages(
+ biometricHint: l10n.androidBiometricHint,
+ biometricNotRecognized: l10n.androidBiometricNotRecognized,
+ biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
+ biometricSuccess: l10n.androidBiometricSuccess,
+ cancelButton: l10n.androidCancelButton,
+ deviceCredentialsRequiredTitle:
+ l10n.androidDeviceCredentialsRequiredTitle,
+ deviceCredentialsSetupDescription:
+ l10n.androidDeviceCredentialsSetupDescription,
+ goToSettingsButton: l10n.goToSettings,
+ goToSettingsDescription: l10n.androidGoToSettingsDescription,
+ signInTitle: l10n.androidSignInTitle,
+ ),
+ IOSAuthMessages(
+ goToSettingsButton: l10n.goToSettings,
+ goToSettingsDescription: l10n.goToSettings,
+ lockOut: l10n.iOSLockOut,
+ // cancelButton default value is "Ok"
+ cancelButton: l10n.iOSOkButton,
+ ),
+ ],
+ );
+ }
}
diff --git a/auth/lib/utils/crypto_util.dart b/auth/lib/utils/crypto_util.dart
deleted file mode 100644
index 494ef130f..000000000
--- a/auth/lib/utils/crypto_util.dart
+++ /dev/null
@@ -1,509 +0,0 @@
-import 'dart:convert';
-import 'dart:io' as io;
-import 'dart:typed_data';
-
-import 'package:computer/computer.dart';
-import 'package:ente_auth/core/errors.dart';
-import 'package:ente_auth/models/derived_key_result.dart';
-import 'package:ente_auth/models/encryption_result.dart';
-import 'package:ente_auth/utils/device_info.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
-import 'package:logging/logging.dart';
-
-const int encryptionChunkSize = 4 * 1024 * 1024;
-final int decryptionChunkSize =
- encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
-const int hashChunkSize = 4 * 1024 * 1024;
-const int loginSubKeyLen = 32;
-const int loginSubKeyId = 1;
-const String loginSubKeyContext = "loginctx";
-
-Uint8List cryptoSecretboxEasy(Map args) {
- return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
-}
-
-Uint8List cryptoSecretboxOpenEasy(Map args) {
- return Sodium.cryptoSecretboxOpenEasy(
- args["cipher"],
- args["nonce"],
- args["key"],
- );
-}
-
-Uint8List cryptoPwHash(Map args) {
- return Sodium.cryptoPwhash(
- Sodium.cryptoSecretboxKeybytes,
- args["password"],
- args["salt"],
- args["opsLimit"],
- args["memLimit"],
- Sodium.cryptoPwhashAlgArgon2id13,
- );
-}
-
-Uint8List cryptoKdfDeriveFromKey(
- Map args,
-) {
- return Sodium.cryptoKdfDeriveFromKey(
- args["subkeyLen"],
- args["subkeyId"],
- args["context"],
- args["key"],
- );
-}
-
-// Returns the hash for a given file, chunking it in batches of hashChunkSize
-Future cryptoGenericHash(Map args) async {
- final sourceFile = io.File(args["sourceFilePath"]);
- final sourceFileLength = await sourceFile.length();
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final state =
- Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
- var bytesRead = 0;
- bool isDone = false;
- while (!isDone) {
- var chunkSize = hashChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- isDone = true;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- Sodium.cryptoGenerichashUpdate(state, buffer);
- }
- await inputFile.close();
- return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax);
-}
-
-EncryptionResult chachaEncryptData(Map args) {
- final initPushResult =
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
- final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
- initPushResult.state,
- args["source"],
- null,
- Sodium.cryptoSecretstreamXchacha20poly1305TagFinal,
- );
- return EncryptionResult(
- encryptedData: encryptedData,
- header: initPushResult.header,
- );
-}
-
-// Encrypts a given file, in chunks of encryptionChunkSize
-Future chachaEncryptFile(Map args) async {
- final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
- final logger = Logger("ChaChaEncrypt");
- final sourceFile = io.File(args["sourceFilePath"]);
- final destinationFile = io.File(args["destinationFilePath"]);
- final sourceFileLength = await sourceFile.length();
- logger.info("Encrypting file of size " + sourceFileLength.toString());
-
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
- final initPushResult =
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
- var bytesRead = 0;
- var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
- while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
- var chunkSize = encryptionChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
- initPushResult.state,
- buffer,
- null,
- tag,
- );
- await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append);
- }
- await inputFile.close();
-
- logger.info(
- "Encryption time: " +
- (DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
- .toString(),
- );
-
- return EncryptionResult(key: key, header: initPushResult.header);
-}
-
-Future chachaDecryptFile(Map args) async {
- final logger = Logger("ChaChaDecrypt");
- final decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
- final sourceFile = io.File(args["sourceFilePath"]);
- final destinationFile = io.File(args["destinationFilePath"]);
- final sourceFileLength = await sourceFile.length();
- logger.info("Decrypting file of size " + sourceFileLength.toString());
-
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
- args["header"],
- args["key"],
- );
-
- var bytesRead = 0;
- var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
- while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
- var chunkSize = decryptionChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- final pullResult =
- Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
- await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
- tag = pullResult.tag;
- }
- inputFile.closeSync();
-
- logger.info(
- "ChaCha20 Decryption time: " +
- (DateTime.now().millisecondsSinceEpoch - decryptionStartTime)
- .toString(),
- );
-}
-
-Uint8List chachaDecryptData(Map args) {
- final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
- args["header"],
- args["key"],
- );
- final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
- pullState,
- args["source"],
- null,
- );
- return pullResult.m;
-}
-
-class CryptoUtil {
- // Note: workers are turned on during app startup.
- static final Computer _computer = Computer.shared();
-
- static init() {
- Sodium.init();
- }
-
- static Uint8List base642bin(
- String b64, {
- String? ignore,
- int variant = Sodium.base64VariantOriginal,
- }) {
- return Sodium.base642bin(b64, ignore: ignore, variant: variant);
- }
-
- static String bin2base64(
- Uint8List bin, {
- bool urlSafe = false,
- }) {
- return Sodium.bin2base64(
- bin,
- variant:
- urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
- );
- }
-
- static String bin2hex(Uint8List bin) {
- return Sodium.bin2hex(bin);
- }
-
- static Uint8List hex2bin(String hex) {
- return Sodium.hex2bin(hex);
- }
-
- // Encrypts the given source, with the given key and a randomly generated
- // nonce, using XSalsa20 (w Poly1305 MAC).
- // This function runs on the same thread as the caller, so should be used only
- // for small amounts of data where thread switching can result in a degraded
- // user experience
- static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
- final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
-
- final args = {};
- args["source"] = source;
- args["nonce"] = nonce;
- args["key"] = key;
- final encryptedData = cryptoSecretboxEasy(args);
- return EncryptionResult(
- key: key,
- nonce: nonce,
- encryptedData: encryptedData,
- );
- }
-
- // Decrypts the given cipher, with the given key and nonce using XSalsa20
- // (w Poly1305 MAC).
- static Future decrypt(
- Uint8List cipher,
- Uint8List key,
- Uint8List nonce,
- ) async {
- final args = {};
- args["cipher"] = cipher;
- args["nonce"] = nonce;
- args["key"] = key;
- return _computer.compute(
- cryptoSecretboxOpenEasy,
- param: args,
- taskName: "decrypt",
- );
- }
-
- // Decrypts the given cipher, with the given key and nonce using XSalsa20
- // (w Poly1305 MAC).
- // This function runs on the same thread as the caller, so should be used only
- // for small amounts of data where thread switching can result in a degraded
- // user experience
- static Uint8List decryptSync(
- Uint8List cipher,
- Uint8List key,
- Uint8List nonce,
- ) {
- final args = {};
- args["cipher"] = cipher;
- args["nonce"] = nonce;
- args["key"] = key;
- return cryptoSecretboxOpenEasy(args);
- }
-
- // Encrypts the given source, with the given key and a randomly generated
- // nonce, using XChaCha20 (w Poly1305 MAC).
- // This function runs on the isolate pool held by `_computer`.
- // TODO: Remove "ChaCha", an implementation detail from the function name
- static Future encryptChaCha(
- Uint8List source,
- Uint8List key,
- ) async {
- final args = {};
- args["source"] = source;
- args["key"] = key;
- return _computer.compute(
- chachaEncryptData,
- param: args,
- taskName: "encryptChaCha",
- );
- }
-
- // Decrypts the given source, with the given key and header using XChaCha20
- // (w Poly1305 MAC).
- // TODO: Remove "ChaCha", an implementation detail from the function name
- static Future decryptChaCha(
- Uint8List source,
- Uint8List key,
- Uint8List header,
- ) async {
- final args = {};
- args["source"] = source;
- args["key"] = key;
- args["header"] = header;
- return _computer.compute(
- chachaDecryptData,
- param: args,
- taskName: "decryptChaCha",
- );
- }
-
- // Encrypts the file at sourceFilePath, with the key (if provided) and a
- // randomly generated nonce using XChaCha20 (w Poly1305 MAC), and writes it
- // to the destinationFilePath.
- // If a key is not provided, one is generated and returned.
- static Future encryptFile(
- String sourceFilePath,
- String destinationFilePath, {
- Uint8List? key,
- }) {
- final args = {};
- args["sourceFilePath"] = sourceFilePath;
- args["destinationFilePath"] = destinationFilePath;
- args["key"] = key;
- return _computer.compute(
- chachaEncryptFile,
- param: args,
- taskName: "encryptFile",
- );
- }
-
- // Decrypts the file at sourceFilePath, with the given key and header using
- // XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
- static Future decryptFile(
- String sourceFilePath,
- String destinationFilePath,
- Uint8List header,
- Uint8List key,
- ) {
- final args = {};
- args["sourceFilePath"] = sourceFilePath;
- args["destinationFilePath"] = destinationFilePath;
- args["header"] = header;
- args["key"] = key;
- return _computer.compute(
- chachaDecryptFile,
- param: args,
- taskName: "decryptFile",
- );
- }
-
- // Generates and returns a 256-bit key.
- static Uint8List generateKey() {
- return Sodium.cryptoSecretboxKeygen();
- }
-
- // Generates and returns a random byte buffer of length
- // crypto_pwhash_SALTBYTES (16)
- static Uint8List getSaltToDeriveKey() {
- return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
- }
-
- // Generates and returns a secret key and the corresponding public key.
- static Future generateKeyPair() async {
- return Sodium.cryptoBoxKeypair();
- }
-
- // Decrypts the input using the given publicKey-secretKey pair
- static Uint8List openSealSync(
- Uint8List input,
- Uint8List publicKey,
- Uint8List secretKey,
- ) {
- return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
- }
-
- // Encrypts the input using the given publicKey
- static Uint8List sealSync(Uint8List input, Uint8List publicKey) {
- return Sodium.cryptoBoxSeal(input, publicKey);
- }
-
- // Derives a key for a given password and salt using Argon2id, v1.3.
- // The function first attempts to derive a key with both memLimit and opsLimit
- // set to their Sensitive variants.
- // If this fails, say on a device with insufficient RAM, we retry by halving
- // the memLimit and doubling the opsLimit, while ensuring that we stay within
- // the min and max limits for both parameters.
- // At all points, we ensure that the product of these two variables (the area
- // under the graph that determines the amount of work required) is a constant.
- static Future deriveSensitiveKey(
- Uint8List password,
- Uint8List salt,
- ) async {
- final logger = Logger("pwhash");
- int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
- int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
- if (await isLowSpecDevice()) {
- logger.info("low spec device detected");
- // When sensitive memLimit (1 GB) is used, on low spec device the OS might
- // kill the app with OOM. To avoid that, start with 256 MB and
- // corresponding ops limit (16).
- // This ensures that the product of these two variables
- // (the area under the graph that determines the amount of work required)
- // stays the same
- // SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: 1073741824
- // SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE: 268435456
- // SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE: 4
- memLimit = Sodium.cryptoPwhashMemlimitModerate;
- final factor = Sodium.cryptoPwhashMemlimitSensitive ~/
- Sodium.cryptoPwhashMemlimitModerate; // = 4
- opsLimit = opsLimit * factor; // = 16
- }
- Uint8List key;
- while (memLimit >= Sodium.cryptoPwhashMemlimitMin &&
- opsLimit <= Sodium.cryptoPwhashOpslimitMax) {
- try {
- key = await deriveKey(password, salt, memLimit, opsLimit);
- return DerivedKeyResult(key, memLimit, opsLimit);
- } catch (e, s) {
- logger.warning(
- "failed to deriveKey mem: $memLimit, ops: $opsLimit",
- e,
- s,
- );
- }
- memLimit = (memLimit / 2).round();
- opsLimit = opsLimit * 2;
- }
- throw UnsupportedError("Cannot perform this operation on this device");
- }
-
- // Derives a key for the given password and salt, using Argon2id, v1.3
- // with memory and ops limit hardcoded to their Interactive variants
- // NOTE: This is only used while setting passwords for shared links, as an
- // extra layer of authentication (atop the access token and collection key).
- // More details @ https://ente.io/blog/building-shareable-links/
- static Future deriveInteractiveKey(
- Uint8List password,
- Uint8List salt,
- ) async {
- final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
- final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
- final key = await deriveKey(password, salt, memLimit, opsLimit);
- return DerivedKeyResult(key, memLimit, opsLimit);
- }
-
- // Derives a key for a given password, salt, memLimit and opsLimit using
- // Argon2id, v1.3.
- static Future deriveKey(
- Uint8List password,
- Uint8List salt,
- int memLimit,
- int opsLimit,
- ) async {
- try {
- return await _computer.compute(
- cryptoPwHash,
- param: {
- "password": password,
- "salt": salt,
- "memLimit": memLimit,
- "opsLimit": opsLimit,
- },
- taskName: "deriveKey",
- );
- } catch (e, s) {
- final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
- 'opsLimit: $opsLimit';
- Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
- throw KeyDerivationError();
- }
- }
-
- // derives a Login key as subKey from the given key by applying KDF
- // (Key Derivation Function) with the `loginSubKeyId` and
- // `loginSubKeyLen` and `loginSubKeyContext` as context
- static Future