Merge remote-tracking branch 'origin/mobile_face' into mobile_face
BIN
.github/assets/github-badge.png
vendored
Before Width: | Height: | Size: 12 KiB |
4
.github/workflows/auth-crowdin.yml
vendored
|
@ -2,12 +2,12 @@ name: "Sync Crowdin translations (auth)"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
# Run workflow when auth's intl_en.arb is changed
|
# Run workflow when auth's intl_en.arb is changed
|
||||||
- "mobile/lib/l10n/arb/app_en.arb"
|
- "mobile/lib/l10n/arb/app_en.arb"
|
||||||
# Or the workflow itself is changed
|
# Or the workflow itself is changed
|
||||||
- ".github/workflows/auth-crowdin.yml"
|
- ".github/workflows/auth-crowdin.yml"
|
||||||
branches: [main]
|
|
||||||
schedule:
|
schedule:
|
||||||
# See: [Note: Run workflow on specific days of the week]
|
# See: [Note: Run workflow on specific days of the week]
|
||||||
- cron: "50 1 * * 2,5"
|
- cron: "50 1 * * 2,5"
|
||||||
|
@ -28,7 +28,7 @@ jobs:
|
||||||
base_path: "auth/"
|
base_path: "auth/"
|
||||||
config: "auth/crowdin.yml"
|
config: "auth/crowdin.yml"
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: true
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
localization_branch_name: crowdin-translations-auth
|
localization_branch_name: crowdin-translations-auth
|
||||||
create_pull_request: true
|
create_pull_request: true
|
||||||
|
|
4
.github/workflows/auth-lint.yml
vendored
|
@ -3,13 +3,13 @@ name: "Lint (auth)"
|
||||||
on:
|
on:
|
||||||
# Run on every push to a branch other than main that changes auth/
|
# Run on every push to a branch other than main that changes auth/
|
||||||
push:
|
push:
|
||||||
branches-ignore: [main]
|
branches-ignore: [main, "deploy/**"]
|
||||||
paths:
|
paths:
|
||||||
- "auth/**"
|
- "auth/**"
|
||||||
- ".github/workflows/auth-lint.yml"
|
- ".github/workflows/auth-lint.yml"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.16.9"
|
FLUTTER_VERSION: "3.19.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
|
34
.github/workflows/auth-release.yml
vendored
|
@ -29,11 +29,11 @@ on:
|
||||||
- "auth-v*"
|
- "auth-v*"
|
||||||
|
|
||||||
env:
|
env:
|
||||||
FLUTTER_VERSION: "3.13.4"
|
FLUTTER_VERSION: "3.19.3"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-ubuntu:
|
build-ubuntu:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
|
@ -72,6 +72,8 @@ jobs:
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||||
|
|
||||||
- name: Build PlayStore AAB
|
- 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: |
|
run: |
|
||||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||||
env:
|
env:
|
||||||
|
@ -83,7 +85,7 @@ jobs:
|
||||||
- name: Install dependencies for desktop build
|
- name: Install dependencies for desktop build
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update -y
|
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
|
- name: Install appimagetool
|
||||||
run: |
|
run: |
|
||||||
|
@ -92,8 +94,6 @@ jobs:
|
||||||
mv appimagetool /usr/local/bin/
|
mv appimagetool /usr/local/bin/
|
||||||
|
|
||||||
- name: Build desktop app
|
- name: Build desktop app
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
run: |
|
run: |
|
||||||
flutter config --enable-linux-desktop
|
flutter config --enable-linux-desktop
|
||||||
dart pub global activate flutter_distributor
|
dart pub global activate flutter_distributor
|
||||||
|
@ -118,6 +118,8 @@ jobs:
|
||||||
updateOnlyUnreleased: true
|
updateOnlyUnreleased: true
|
||||||
|
|
||||||
- name: Upload AAB to PlayStore
|
- 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
|
uses: r0adkll/upload-google-play@v1
|
||||||
with:
|
with:
|
||||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||||
|
@ -149,8 +151,6 @@ jobs:
|
||||||
run: mkdir artifacts
|
run: mkdir artifacts
|
||||||
|
|
||||||
- name: Build Windows installer
|
- name: Build Windows installer
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
run: |
|
run: |
|
||||||
flutter config --enable-windows-desktop
|
flutter config --enable-windows-desktop
|
||||||
dart pub global activate flutter_distributor
|
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
|
mv dist/**/ente_auth-*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||||
|
|
||||||
- name: Retain Windows EXE and DLLs
|
- 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
|
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||||
|
|
||||||
- name: Code sign Windows installer and EXE
|
- name: Code sign Windows installer and EXE
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
uses: dlemstra/code-sign-action@v1
|
uses: dlemstra/code-sign-action@v1
|
||||||
with:
|
with:
|
||||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||||
|
@ -175,9 +171,10 @@ jobs:
|
||||||
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||||
|
|
||||||
- name: Zip Windows EXE and DLLs
|
- name: Zip Windows EXE and DLLs
|
||||||
# Temporarily disable desktop builds
|
run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows
|
||||||
if: false
|
|
||||||
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
|
- name: Generate checksums
|
||||||
|
run: sha256sum artifacts/ente-* > artifacts/sha256sum-windows
|
||||||
|
|
||||||
- name: Create a draft GitHub release
|
- name: Create a draft GitHub release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
|
@ -248,8 +245,6 @@ jobs:
|
||||||
run: mkdir artifacts
|
run: mkdir artifacts
|
||||||
|
|
||||||
- name: Build macOS DMG
|
- name: Build macOS DMG
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
run: |
|
run: |
|
||||||
flutter config --enable-macos-desktop
|
flutter config --enable-macos-desktop
|
||||||
dart pub global activate flutter_distributor
|
dart pub global activate flutter_distributor
|
||||||
|
@ -257,16 +252,12 @@ jobs:
|
||||||
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
|
mv dist/**/ente_auth-*-macos.dmg artifacts/ente-${{ github.ref_name }}.dmg
|
||||||
|
|
||||||
- name: Code sign DMG
|
- name: Code sign DMG
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
run: |
|
run: |
|
||||||
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
|
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 --force --timestamp --sign "$CERT_NAME" --options runtime artifacts/ente-${{ github.ref_name }}.dmg
|
||||||
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
|
codesign --verify --verbose=4 artifacts/ente-${{ github.ref_name }}.dmg
|
||||||
|
|
||||||
- name: Notarize and staple DMG
|
- name: Notarize and staple DMG
|
||||||
# Temporarily disable desktop builds
|
|
||||||
if: false
|
|
||||||
run: |
|
run: |
|
||||||
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
|
xcrun notarytool submit artifacts/ente-${{ github.ref_name }}.dmg \
|
||||||
--wait \
|
--wait \
|
||||||
|
@ -279,6 +270,9 @@ jobs:
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
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
|
- name: Create a draft GitHub release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
with:
|
with:
|
||||||
|
|
24
.github/workflows/copycat-db-release.yaml
vendored
Normal file
|
@ -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 }}
|
2
.github/workflows/docs-verify-build.yml
vendored
|
@ -6,7 +6,7 @@ name: "Verify build (docs)"
|
||||||
on:
|
on:
|
||||||
# Run on every push to a branch other than main that changes docs/
|
# Run on every push to a branch other than main that changes docs/
|
||||||
push:
|
push:
|
||||||
branches-ignore: [main]
|
branches-ignore: [main, "deploy/**"]
|
||||||
paths:
|
paths:
|
||||||
- "docs/**"
|
- "docs/**"
|
||||||
- ".github/workflows/docs-verify-build.yml"
|
- ".github/workflows/docs-verify-build.yml"
|
||||||
|
|
4
.github/workflows/mobile-crowdin.yml
vendored
|
@ -2,12 +2,12 @@ name: "Sync Crowdin translations (mobile)"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
# Run workflow when mobiles's intl_en.arb is changed
|
# Run workflow when mobiles's intl_en.arb is changed
|
||||||
- "mobile/lib/l10n/intl_en.arb"
|
- "mobile/lib/l10n/intl_en.arb"
|
||||||
# Or the workflow itself is changed
|
# Or the workflow itself is changed
|
||||||
- ".github/workflows/mobile-crowdin.yml"
|
- ".github/workflows/mobile-crowdin.yml"
|
||||||
branches: [main]
|
|
||||||
schedule:
|
schedule:
|
||||||
# See: [Note: Run workflow on specific days of the week]
|
# See: [Note: Run workflow on specific days of the week]
|
||||||
- cron: "40 1 * * 2,5"
|
- cron: "40 1 * * 2,5"
|
||||||
|
@ -28,7 +28,7 @@ jobs:
|
||||||
base_path: "mobile/"
|
base_path: "mobile/"
|
||||||
config: "mobile/crowdin.yml"
|
config: "mobile/crowdin.yml"
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: true
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
localization_branch_name: crowdin-translations-mobile
|
localization_branch_name: crowdin-translations-mobile
|
||||||
create_pull_request: true
|
create_pull_request: true
|
||||||
|
|
2
.github/workflows/mobile-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (mobile)"
|
||||||
on:
|
on:
|
||||||
# Run on every push to a branch other than main that changes mobile/
|
# Run on every push to a branch other than main that changes mobile/
|
||||||
push:
|
push:
|
||||||
branches-ignore: [main, f-droid]
|
branches-ignore: [main, f-droid, "deploy/**"]
|
||||||
paths:
|
paths:
|
||||||
- "mobile/**"
|
- "mobile/**"
|
||||||
- ".github/workflows/mobile-lint.yml"
|
- ".github/workflows/mobile-lint.yml"
|
||||||
|
|
2
.github/workflows/server-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (server)"
|
||||||
on:
|
on:
|
||||||
# Run on every push to a branch other than main that changes server/
|
# Run on every push to a branch other than main that changes server/
|
||||||
push:
|
push:
|
||||||
branches-ignore: [main]
|
branches-ignore: [main, "deploy/**"]
|
||||||
paths:
|
paths:
|
||||||
- "server/**"
|
- "server/**"
|
||||||
- ".github/workflows/server-lint.yml"
|
- ".github/workflows/server-lint.yml"
|
||||||
|
|
38
.github/workflows/server-publish.yml
vendored
Normal file
|
@ -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 }}
|
8
.github/workflows/server-release.yml
vendored
|
@ -7,11 +7,11 @@ jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout code
|
||||||
name: Check out code
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: mr-smithers-excellent/docker-build-push@v6
|
- name: Build and push
|
||||||
name: Build & Push
|
uses: mr-smithers-excellent/docker-build-push@v6
|
||||||
with:
|
with:
|
||||||
dockerfile: server/Dockerfile
|
dockerfile: server/Dockerfile
|
||||||
directory: server
|
directory: server
|
||||||
|
|
4
.github/workflows/web-crowdin.yml
vendored
|
@ -2,12 +2,12 @@ name: "Sync Crowdin translations (web)"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
# Run workflow when web's en-US/translation.json is changed
|
# Run workflow when web's en-US/translation.json is changed
|
||||||
- "web/apps/photos/public/locales/en-US/translation.json"
|
- "web/apps/photos/public/locales/en-US/translation.json"
|
||||||
# Or the workflow itself is changed
|
# Or the workflow itself is changed
|
||||||
- ".github/workflows/web-crowdin.yml"
|
- ".github/workflows/web-crowdin.yml"
|
||||||
branches: [main]
|
|
||||||
schedule:
|
schedule:
|
||||||
# [Note: Run workflow on specific days of the week]
|
# [Note: Run workflow on specific days of the week]
|
||||||
#
|
#
|
||||||
|
@ -34,7 +34,7 @@ jobs:
|
||||||
base_path: "web/"
|
base_path: "web/"
|
||||||
config: "web/crowdin.yml"
|
config: "web/crowdin.yml"
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
upload_translations: true
|
upload_translations: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
localization_branch_name: crowdin-translations-web
|
localization_branch_name: crowdin-translations-web
|
||||||
create_pull_request: true
|
create_pull_request: true
|
||||||
|
|
43
.github/workflows/web-deploy-payments.yml
vendored
Normal file
|
@ -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"
|
2
.github/workflows/web-lint.yml
vendored
|
@ -3,7 +3,7 @@ name: "Lint (web)"
|
||||||
on:
|
on:
|
||||||
# Run on every push to a branch other than main that changes web/
|
# Run on every push to a branch other than main that changes web/
|
||||||
push:
|
push:
|
||||||
branches-ignore: [main]
|
branches-ignore: [main, "deploy/**"]
|
||||||
paths:
|
paths:
|
||||||
- "web/**"
|
- "web/**"
|
||||||
- ".github/workflows/web-lint.yml"
|
- ".github/workflows/web-lint.yml"
|
||||||
|
|
13
.github/workflows/web-nightly.yml
vendored
|
@ -78,6 +78,19 @@ jobs:
|
||||||
directory: web/apps/cast/out
|
directory: web/apps/cast/out
|
||||||
wranglerVersion: "3"
|
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
|
- name: Build photos
|
||||||
run: yarn build:photos
|
run: yarn build:photos
|
||||||
env:
|
env:
|
||||||
|
|
1
.github/workflows/web-preview.yml
vendored
|
@ -12,6 +12,7 @@ on:
|
||||||
- "accounts"
|
- "accounts"
|
||||||
- "auth"
|
- "auth"
|
||||||
- "cast"
|
- "cast"
|
||||||
|
- "payments"
|
||||||
- "photos"
|
- "photos"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
|
@ -70,7 +70,7 @@ existing users will be grandfathered in.
|
||||||
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
[<img height="42" src=".github/assets/app-store-badge.svg">](https://apps.apple.com/app/id6444121398)
|
||||||
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
[<img height="42" src=".github/assets/play-store-badge.png">](https://play.google.com/store/apps/details?id=io.ente.auth)
|
||||||
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
[<img height="42" src=".github/assets/f-droid-badge.png">](https://f-droid.org/packages/io.ente.auth/)
|
||||||
[<img height="42" src=".github/assets/github-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
[<img height="42" src=".github/assets/desktop-badge.png">](https://github.com/ente-io/ente/releases?q=tag%3Aauth-v2)
|
||||||
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
|
[<img height="42" src=".github/assets/web-badge.svg">](https://auth.ente.io)
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
6
auth/.gitignore
vendored
|
@ -18,6 +18,11 @@
|
||||||
*.iws
|
*.iws
|
||||||
.idea/
|
.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
|
# Flutter/Dart/Pub related
|
||||||
**/doc/api/
|
**/doc/api/
|
||||||
.dart_tool/
|
.dart_tool/
|
||||||
|
@ -35,3 +40,4 @@ lib/generated_plugin_registrant.dart
|
||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
|
|
||||||
android/key.properties
|
android/key.properties
|
||||||
|
dist/
|
|
@ -1,11 +1,11 @@
|
||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# 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:
|
version:
|
||||||
revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
revision: "ba393198430278b6595976de84fe170f553cc728"
|
||||||
channel: unknown
|
channel: "[user-branch]"
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
@ -13,17 +13,26 @@ project_type: app
|
||||||
migration:
|
migration:
|
||||||
platforms:
|
platforms:
|
||||||
- platform: root
|
- platform: root
|
||||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
- platform: android
|
||||||
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
- platform: ios
|
||||||
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
- platform: linux
|
- platform: linux
|
||||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
- platform: macos
|
- platform: macos
|
||||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
- platform: web
|
||||||
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
- platform: windows
|
- platform: windows
|
||||||
create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
create_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
|
base_revision: ba393198430278b6595976de84fe170f553cc728
|
||||||
|
|
||||||
# User provided section
|
# User provided section
|
||||||
|
|
||||||
|
|
|
@ -31,14 +31,16 @@ You can alternatively install the build from PlayStore or F-Droid.
|
||||||
<img height="59" src="../.github/assets/app-store-badge.svg">
|
<img height="59" src="../.github/assets/app-store-badge.svg">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
### 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
|
### Web
|
||||||
|
|
||||||
You can view your 2FA codes at [auth.ente.io](https://auth.ente.io). For adding
|
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.
|
or managing your secrets, please use our mobile or desktop app.
|
||||||
|
|
||||||
### Desktop
|
|
||||||
|
|
||||||
A native desktop app is coming soon!
|
|
||||||
|
|
||||||
## 🧑💻 Build from source
|
## 🧑💻 Build from source
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 34
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
@ -46,7 +46,7 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "io.ente.auth"
|
applicationId "io.ente.auth"
|
||||||
minSdkVersion 20
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
@ -109,6 +109,7 @@ dependencies {
|
||||||
implementation 'io.sentry:sentry-android:2.0.0'
|
implementation 'io.sentry:sentry-android:2.0.0'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
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'
|
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "BorgBase",
|
"title": "BorgBase",
|
||||||
"altNames": ["borg"],
|
"altNames": [
|
||||||
|
"borg"
|
||||||
|
],
|
||||||
"slug": "BorgBase"
|
"slug": "BorgBase"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -46,11 +48,17 @@
|
||||||
{
|
{
|
||||||
"title": "Bybit"
|
"title": "Bybit"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "CERN"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Channel Island Hosting",
|
"title": "Channel Island Hosting",
|
||||||
"slug": "cih",
|
"slug": "cih",
|
||||||
"hex": "D14633"
|
"hex": "D14633"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "ConfigCat"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Cloudflare"
|
"title": "Cloudflare"
|
||||||
},
|
},
|
||||||
|
@ -62,6 +70,13 @@
|
||||||
{
|
{
|
||||||
"title": "Crowdpear"
|
"title": "Crowdpear"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "DCS",
|
||||||
|
"altNames": [
|
||||||
|
"Digital Combat Simulator"
|
||||||
|
],
|
||||||
|
"slug": "dcs"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "DEGIRO"
|
"title": "DEGIRO"
|
||||||
},
|
},
|
||||||
|
@ -107,9 +122,14 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Gosuslugi",
|
"title": "Gosuslugi",
|
||||||
"altNames": ["Госуслуги"],
|
"altNames": [
|
||||||
|
"Госуслуги"
|
||||||
|
],
|
||||||
"slug": "Gosuslugi"
|
"slug": "Gosuslugi"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Habbo"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Healthchecks.io",
|
"title": "Healthchecks.io",
|
||||||
"slug": "healthchecks"
|
"slug": "healthchecks"
|
||||||
|
@ -172,13 +192,24 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Mastodon",
|
"title": "Mastodon",
|
||||||
"altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"],
|
"altNames": [
|
||||||
|
"mstdn",
|
||||||
|
"fediscience",
|
||||||
|
"mathstodon",
|
||||||
|
"fosstodon"
|
||||||
|
],
|
||||||
"slug": "mastodon",
|
"slug": "mastodon",
|
||||||
"hex": "6364FF"
|
"hex": "6364FF"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Mercado Livre",
|
||||||
|
"slug": "mercado_livre"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Murena",
|
"title": "Murena",
|
||||||
"altNames": ["eCloud"],
|
"altNames": [
|
||||||
|
"eCloud"
|
||||||
|
],
|
||||||
"slug": "ecloud"
|
"slug": "ecloud"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -267,11 +298,18 @@
|
||||||
"title": "Revolt",
|
"title": "Revolt",
|
||||||
"hex": "858585"
|
"hex": "858585"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Rockstar Games",
|
||||||
|
"slug": "rockstar_games"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Rust Language Forum",
|
"title": "Rust Language Forum",
|
||||||
"slug": "rust_language_forum",
|
"slug": "rust_language_forum",
|
||||||
"hex": "000000"
|
"hex": "000000"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Sendgrid"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "service-bw"
|
"title": "service-bw"
|
||||||
},
|
},
|
||||||
|
@ -356,14 +394,23 @@
|
||||||
{
|
{
|
||||||
"title": "Wise"
|
"title": "Wise"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "WYZE",
|
||||||
|
"slug": "wyze"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "X",
|
"title": "X",
|
||||||
"altNames": ["twitter"],
|
"altNames": [
|
||||||
|
"twitter"
|
||||||
|
],
|
||||||
"slug": "x"
|
"slug": "x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Yandex",
|
"title": "Yandex",
|
||||||
"altNames": ["Ya", "Яндекс"],
|
"altNames": [
|
||||||
|
"Ya",
|
||||||
|
"Яндекс"
|
||||||
|
],
|
||||||
"slug": "Yandex"
|
"slug": "Yandex"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
47
auth/assets/custom-icons/icons/CERN.svg
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 283.465 283.465" enable-background="new 0 0 283.465 283.465" xml:space="preserve">
|
||||||
|
<rect x="0.752" y="-0.337" fill="#FFFFFF" width="283.465" height="283.465"/>
|
||||||
|
<path fill="#0033A0" d="M210.282,120.915c0.429,24.998-11.023,41.967-14.629,47.856c-3.143,5.13-10.654,15.024-25.11,30.374
|
||||||
|
c-18.235,19.365-75.231,79.947-79.029,83.983h154.558l-35.636-162.227L210.282,120.915z M224.498,203.72l1.277,5.803
|
||||||
|
c-15.302,16.528-38.73,28.912-66,28.912c-5.841,0-11.941-0.565-18.719-2.005c1.375-1.463,2.693-2.867,3.937-4.188
|
||||||
|
c4.352,0.773,9.361,1.284,14.506,1.286C186.023,233.541,209.198,221.605,224.498,203.72z M0.752-0.337v283.465h84.595l83.686-88.992
|
||||||
|
l-0.117-0.107c-13.455,9.491-30.532,14.646-48.943,14.646c-34.121,0-64.191-20.244-77.232-45.437l-0.132,0.088l17.755,59.9h-4.806
|
||||||
|
c0,0-8.809-30.352-16.33-56.812c-5.692-20.026-9.198-34.861-9.154-48.029c0.156-45.284,35.77-86.809,83.128-89.874
|
||||||
|
c1.3-0.086,5.328-0.506,11.328-0.511c36.48-0.022,148.884,0.84,159.687,0.923v-29.26H0.752z M100.261,210.453
|
||||||
|
c6.786,5.88,17.542,13.273,30.838,18.05c-1.113,1.185-2.321,2.469-3.603,3.833c-13.279-5.257-26.271-13.409-36.369-24.392
|
||||||
|
C93.969,208.901,97.099,209.768,100.261,210.453z M145.883,32.427c13.977,3.923,27.086,11.109,37.504,20.825
|
||||||
|
c-3.006-0.803-6.071-1.451-9.188-1.939c-14.647-11.578-33.714-18.482-53.64-18.482c-47.069,0-85.592,38.386-85.592,85.565
|
||||||
|
c0,47.18,38.382,85.564,85.565,85.564c47.179,0,85.565-38.384,85.565-85.564c0-18.252-6.876-36.487-16.318-49.367
|
||||||
|
c2.255,0.678,5.004,1.841,8.162,3.708c6.393,9.69,12.32,25.515,17.398,49.787c5.33,25.462,32.732,147.426,35.694,160.605h33.182
|
||||||
|
V33.152l-138.333-0.85C145.883,32.303,145.883,32.311,145.883,32.427z M50.595,116.383c0-11.441,8.652-19.051,20.412-19.051
|
||||||
|
c4.577,0,9.814,1.409,12.738,2.664c-0.611,1.353-1.113,3.142-1.326,4.258l-0.319,0.106c-2.261-2.503-5.898-4.672-11.279-4.672
|
||||||
|
c-6.83,0-14.689,5.528-14.689,16.557c0,10.737,8.009,16.442,15.169,16.442c6.434,0,9.513-3,12.278-5.337l0.212,0.213l-0.783,4.172
|
||||||
|
c-1.268,0.96-5.664,3.695-12.279,3.695C58.754,135.429,50.595,127.885,50.595,116.383z M78.765,187.656
|
||||||
|
c-6.344-12.899-9.219-25.995-9.6-38.519c1.612,0,3.481,0.067,5.093,0.067c0.556,11.987,3.274,26.892,12.648,43.087
|
||||||
|
C83.521,191.047,80.974,189.405,78.765,187.656z M112.787,134.755c0,0.001,0,0.001,0,0.001c-1.911-0.098-4.565-0.176-7.084-0.221
|
||||||
|
c-1.451-0.024-2.861-0.041-3.973-0.044c-0.158,0-0.319,0-0.47,0c-3.247,0-8.227,0.105-11.475,0.266
|
||||||
|
c0.214-4.631,0.429-9.26,0.429-13.836v-9.15c0-4.578-0.215-9.206-0.429-13.728c3.193,0.16,8.121,0.265,11.313,0.265
|
||||||
|
c3.193,0,9.149-0.141,10.991-0.265c-0.078,0.498-0.127,1.089-0.127,1.815c0,0.725,0.071,1.473,0.127,1.836
|
||||||
|
c-3.505-0.263-9.766-0.692-16.682-0.692c-0.056,2.287-0.162,11.991-0.162,13.324c6.278,0,10.301-0.269,13.441-0.532
|
||||||
|
c-0.105,0.532-0.16,1.486-0.16,2.017c0,0.532,0.054,1.32,0.16,1.853c-3.671-0.374-11.887-0.48-13.441-0.48
|
||||||
|
c-0.095,1.779-0.012,13.294,0.053,14.266c3.889-0.057,13.857-0.359,17.489-0.693c-0.057,0.403-0.125,1.233-0.125,2.042
|
||||||
|
C112.661,133.608,112.708,134.198,112.787,134.755z M144.039,134.58c-0.485,0-2.321,0.017-3.334,0.177
|
||||||
|
c-2.103-3.205-8.839-13.302-13.419-18.015c-0.137,0-2.708,0.003-2.708,0.003v4.23c0,4.575,0.212,9.205,0.426,13.781
|
||||||
|
c-0.906-0.161-2.542-0.177-2.877-0.177c-0.335,0-1.971,0.017-2.878,0.177c0.214-4.577,0.428-9.206,0.428-13.781v-9.152
|
||||||
|
c0-4.577-0.213-9.207-0.428-13.782c2.024,0.16,4.584,0.265,6.606,0.265c2.021,0,4.043-0.265,6.064-0.265
|
||||||
|
c6.013,0,11.523,1.776,11.523,8.472c0,7.084-7.06,9.632-11.104,10.163c2.606,3.246,11.946,14.619,15.032,18.08
|
||||||
|
C146.309,134.596,144.525,134.58,144.039,134.58z M184.319,135.253l-1.742-0.017c-2.13-2.888-24.461-26.18-26.395-28.237
|
||||||
|
c-0.053,1.967-0.055,6.062-0.055,10.043c0,5.287,0.4,13.354,0.634,17.714c-0.54-0.098-1.338-0.195-2.268-0.195
|
||||||
|
c-0.939,0-1.709,0.086-2.331,0.195c0.436-5.625,0.558-14.755,0.558-23.337c0-6.704-0.099-10.38-0.178-13.762l1.744,0.015
|
||||||
|
c2.256,2.45,24.457,25.428,26.392,27.487c0.053-1.967,0.057-5.441,0.057-9.424c0-5.285-0.402-13.354-0.636-17.711
|
||||||
|
c0.542,0.094,1.338,0.192,2.269,0.192c0.942,0,1.71-0.084,2.332-0.192c-0.438,5.624-0.56,14.753-0.56,23.336
|
||||||
|
C184.14,128.063,184.239,131.868,184.319,135.253z M230.093,88.11c9.889,12.128,17.896,31.314,19.065,47.379h0.156l8.24-85.104
|
||||||
|
l4.646-0.003c0,0-5.271,55.013-8.341,82.351c-3.844,34.241-8.729,48.448-18.071,63.403l-1.43-6.508
|
||||||
|
c7.28-12.787,9.357-23.946,10.306-29.823c2.956-18.304-1.273-43.291-14.025-62.789c-14.291-21.849-39.84-37.924-71.2-37.924
|
||||||
|
c-25.762,0-48.143,11.327-63.766,28.993l-3.789-3.003c16.539-18.764,40.576-30.737,67.558-30.737
|
||||||
|
C187.735,54.346,212.924,67.058,230.093,88.11z M138.305,107.241c0-5.29-4.629-6.973-8.248-6.973c-2.448,0-4.043,0.161-5.162,0.268
|
||||||
|
c-0.159,3.885-0.318,7.457-0.318,11.287v2.926c0.529,0.071,3.021,0.059,3.566,0.049
|
||||||
|
C132.537,114.707,138.305,113.312,138.305,107.241z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.9 KiB |
9
auth/assets/custom-icons/icons/configcat.svg
Normal file
After Width: | Height: | Size: 68 KiB |
9
auth/assets/custom-icons/icons/dcs.svg
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;">
|
||||||
|
<g transform="matrix(1.59257,0,0,1.59257,0,9.06171)">
|
||||||
|
<path d="M0.87,0.08L3.17,0.08C4.2,0.08 5.13,0.51 5.13,1.67C5.13,2.92 3.91,3.6 2.78,3.6L0.06,3.6C0.06,3.6 0.87,0.09 0.87,0.08ZM2.85,2.82C3.44,2.82 4.14,2.39 4.14,1.74C4.14,1.19 3.7,0.85 3.17,0.85L1.71,0.85L1.26,2.81L2.85,2.81L2.85,2.82Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||||
|
<path d="M11.95,1.33C11.87,1.31 11.65,1.26 11.65,1.14C11.65,0.81 12.52,0.81 12.74,0.81C13.25,0.81 13.96,0.91 14.4,1.17L15,0.59C14.39,0.18 13.59,0.04 12.86,0.04C12.07,0.04 10.65,0.18 10.65,1.24C10.65,2.45 13.65,1.96 13.65,2.52C13.65,2.86 12.89,2.88 12.66,2.88C11.9,2.88 11.39,2.74 10.77,2.29C10.57,2.48 10.36,2.67 10.16,2.86C10.95,3.44 11.7,3.64 12.67,3.64C13.4,3.64 14.68,3.36 14.68,2.42C14.68,1.34 12.67,1.5 11.97,1.32L11.95,1.32L11.95,1.33Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||||
|
<path d="M9.18,2.35L9.16,2.35C9.07,2.43 8.41,2.95 7.67,2.87C7.14,2.81 6.41,2.44 6.41,1.95C6.41,1.15 7.26,0.83 7.94,0.83C8.39,0.83 8.82,0.93 9.17,1.18C9.48,1.07 9.8,0.97 10.11,0.86C9.59,0.3 8.82,0.07 8.06,0.07C6.92,0.07 5.43,0.73 5.43,2.06C5.43,3.22 6.65,3.63 7.61,3.63C8.41,3.63 9.18,3.4 9.78,2.88C9.78,2.88 9.18,2.38 9.17,2.37L9.16,2.37L9.18,2.35Z" style="fill:rgb(245,158,15);fill-rule:nonzero;stroke:rgb(245,158,15);stroke-width:0.09px;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
9
auth/assets/custom-icons/icons/habbo.svg
Normal file
After Width: | Height: | Size: 26 KiB |
9
auth/assets/custom-icons/icons/mercado_livre.svg
Normal file
After Width: | Height: | Size: 57 KiB |
1
auth/assets/custom-icons/icons/rockstar_games.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="6.525 7.459 339.266 319.582"><path fill="orange" d="M71.598 11.25H280.72c33.844 0 61.282 25.782 61.282 57.586v196.512c0 31.804-27.437 57.586-61.282 57.586H71.598c-33.845 0-61.28-25.782-61.28-57.586V68.836c0-31.804 27.435-57.586 61.28-57.586z"/><path d="M280.719 326.725H71.598c-35.881 0-65.072-27.533-65.072-61.377V68.836c0-33.844 29.19-61.377 65.072-61.377H280.72c35.88 0 65.072 27.533 65.072 61.377v196.512c0 33.844-29.192 61.377-65.073 61.377zM71.598 15.042c-31.7 0-57.49 24.131-57.49 53.794v196.512c0 29.662 25.79 53.794 57.49 53.794H280.72c31.7 0 57.49-24.132 57.49-53.794V68.836c0-29.662-25.79-53.794-57.49-53.794H71.598z"/><path d="M127.423 64.013l62.975.149c13.161-.099 22.989 2.002 29.48 6.303 7.928 5.272 11.89 14.343 11.89 27.213 0 21.19-9.828 33.195-29.482 36.012v.297c8.667 2.159 13.048 9.335 13.146 21.528 0 6.245-.233 14.245-.7 24 0 6.542 1.384 12.202 4.156 16.98H184.38c-1.611-1.747-2.416-5.305-2.416-10.677.66-8.98.99-16.347.99-22.098 0-11.617-5.582-17.425-16.746-17.425h-22.329l-9.738 46.987h-33.613l26.895-129.269zm53.236 26.941h-24.744l-6.453 31.192h26.775c14.967 0 22.497-5.906 22.595-17.721.001-8.98-6.058-13.47-18.173-13.47z"/><path d="M223.456 196.346l24.915-43.18 6.717 43.478h42.506l-38.349 27.534 6.14 43.18-33.204-26.05-44.633 27.089 20.878-45.973-24.333-26.2 39.363.122zm113.568 113.887c1.38 0 2.726.362 4.04 1.086 1.315.723 2.339 1.76 3.07 3.108.735 1.348 1.101 2.753 1.101 4.216 0 1.449-.36 2.841-1.084 4.177a7.735 7.735 0 01-3.039 3.114c-1.302.74-2.665 1.108-4.09 1.108-1.421 0-2.784-.37-4.089-1.108a7.762 7.762 0 01-3.044-3.114c-.725-1.336-1.089-2.73-1.089-4.177 0-1.463.369-2.868 1.106-4.216.738-1.348 1.763-2.384 3.077-3.108 1.316-.725 2.662-1.086 4.04-1.086zm0 1.391c-1.154 0-2.278.303-3.37.909a6.451 6.451 0 00-2.566 2.594c-.617 1.126-.926 2.297-.926 3.514 0 1.211.304 2.372.91 3.482a6.542 6.542 0 002.542 2.596c1.089.619 2.224.93 3.409.93 1.183 0 2.32-.311 3.41-.93a6.498 6.498 0 002.536-2.596c.603-1.11.904-2.27.904-3.482 0-1.218-.308-2.388-.92-3.514a6.39 6.39 0 00-2.565-2.594c-1.094-.606-2.216-.909-3.364-.909zm-3.604 11.664v-9.045h3.038c1.039 0 1.79.083 2.254.25.466.167.835.459 1.11.875.277.416.415.857.415 1.325 0 .661-.23 1.237-.692 1.726-.46.49-1.072.764-1.835.824.312.135.563.294.75.48.358.356.792.954 1.308 1.792l1.078 1.772h-1.742l-.783-1.426c-.617-1.12-1.115-1.823-1.492-2.105-.262-.209-.643-.313-1.145-.313h-.838v3.844h-1.426zm1.426-5.092h1.732c.829 0 1.393-.126 1.694-.38.3-.25.451-.586.451-1.002a1.24 1.24 0 00-.218-.718 1.302 1.302 0 00-.604-.473c-.258-.103-.734-.157-1.431-.157h-1.624v2.73z"/><path fill="#FFF" d="M252.503 221.088l25.47-18.142h-28.177l-4.881-31.464-17.882 31.168h-28.467l17.302 18.587-14.305 31.193 31.052-18.735 24.48 19.18-4.592-31.787z"/></svg>
|
After Width: | Height: | Size: 2.7 KiB |
9
auth/assets/custom-icons/icons/sendgrid.svg
Normal file
After Width: | Height: | Size: 59 KiB |
1
auth/assets/custom-icons/icons/wyze.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#1DF0BB" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Wyze</title><path d="M5.475 13.171 7.3 9.469h.974L5.779 14.53h-.608l-1.034-2.082-1.034 2.082h-.609L0 9.469h.973l1.826 3.673.851-1.706-.973-1.967h.973l1.825 3.702Zm8.457-3.702-2.251 3.442v1.591h-.882v-1.591L8.517 9.469h1.034l1.673 2.545 1.673-2.545h1.035Zm5.444 4.194H24v.867h-4.624v-.867Zm0-4.194H24v.868h-4.624v-.868Zm0 2.083H24v.867h-4.624v-.867Zm-.273-2.083-3.438 4.223h3.133v.838H13.84l3.407-4.222h-3.042v-.839h4.898Z"/></svg>
|
After Width: | Height: | Size: 523 B |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
auth/assets/icons/auth-icon.ico
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
auth/assets/icons/auth-icon.png
Normal file
After Width: | Height: | Size: 38 KiB |
25
auth/distribute_options.yaml
Normal file
|
@ -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
|
|
@ -37,4 +37,4 @@ file, that adheres to the above format.
|
||||||
SUPPORT
|
SUPPORT
|
||||||
|
|
||||||
If you need help, please reach out to support@ente.io, and a human will get in touch with you.
|
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
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
ente Authenticator
|
Ente Authenticator
|
|
@ -1,6 +1,6 @@
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
android: "launcher_icon"
|
android: "launcher_icon"
|
||||||
image_path: "assets/icon-light.png"
|
image_path: "assets/generation-icons/icon-light.png"
|
||||||
adaptive_icon_foreground: "assets/icon-light-adaptive-fg.png"
|
adaptive_icon_foreground: "assets/generation-icons/icon-light-adaptive-fg.png"
|
||||||
adaptive_icon_background: "#ffffff"
|
adaptive_icon_background: "#ffffff"
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
PODS:
|
PODS:
|
||||||
- connectivity (0.0.1):
|
- app_links (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Reachability
|
- connectivity_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- ReachabilitySwift
|
||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- DKImagePickerController/Core (4.3.4):
|
- DKImagePickerController/Core (4.3.4):
|
||||||
|
@ -45,27 +47,26 @@ PODS:
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
- flutter_email_sender (0.0.1):
|
- flutter_email_sender (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview (0.0.1):
|
- flutter_inappwebview_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_inappwebview/Core (= 0.0.1)
|
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 5.0)
|
||||||
- flutter_inappwebview/Core (0.0.1):
|
- flutter_inappwebview_ios/Core (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (~> 5.0)
|
- OrderedSet (~> 5.0)
|
||||||
|
- flutter_local_authentication (1.2.0):
|
||||||
|
- Flutter
|
||||||
- flutter_local_notifications (0.0.1):
|
- flutter_local_notifications (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_native_splash (0.0.1):
|
- flutter_native_splash (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_secure_storage (6.0.0):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_sodium (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast
|
- Toast
|
||||||
- FMDB (2.7.5):
|
- local_auth_darwin (0.0.1):
|
||||||
- FMDB/standard (= 2.7.5)
|
- Flutter
|
||||||
- FMDB/standard (2.7.5)
|
|
||||||
- local_auth_ios (0.0.1):
|
- local_auth_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- move_to_background (0.0.1):
|
- move_to_background (0.0.1):
|
||||||
|
@ -82,46 +83,65 @@ PODS:
|
||||||
- qr_code_scanner (0.2.0):
|
- qr_code_scanner (0.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- Reachability (3.2)
|
- ReachabilitySwift (5.2.1)
|
||||||
- SDWebImage (5.17.0):
|
- SDWebImage (5.19.0):
|
||||||
- SDWebImage/Core (= 5.17.0)
|
- SDWebImage/Core (= 5.19.0)
|
||||||
- SDWebImage/Core (5.17.0)
|
- SDWebImage/Core (5.19.0)
|
||||||
- Sentry/HybridSDK (8.9.1):
|
- Sentry/HybridSDK (8.21.0):
|
||||||
- SentryPrivate (= 8.9.1)
|
- SentryPrivate (= 8.21.0)
|
||||||
- sentry_flutter (0.0.1):
|
- sentry_flutter (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- Sentry/HybridSDK (= 8.9.1)
|
- Sentry/HybridSDK (= 8.21.0)
|
||||||
- SentryPrivate (8.9.1)
|
- SentryPrivate (8.21.0)
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- smart_auth (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- sodium_libs (2.2.1):
|
||||||
|
- Flutter
|
||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FlutterMacOS
|
||||||
- SwiftyGif (5.4.4)
|
- sqlite3 (3.45.1):
|
||||||
- Toast (4.0.0)
|
- sqlite3/common (= 3.45.1)
|
||||||
- uni_links (0.0.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
|
- 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):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
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`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||||
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
|
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
- 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_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/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`)
|
- 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`)
|
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
|
@ -131,27 +151,31 @@ DEPENDENCIES:
|
||||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
|
||||||
- uni_links (from `.symlinks/plugins/uni_links/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`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- DKImagePickerController
|
- DKImagePickerController
|
||||||
- DKPhotoGallery
|
- DKPhotoGallery
|
||||||
- FMDB
|
|
||||||
- MTBBarcodeScanner
|
- MTBBarcodeScanner
|
||||||
- OrderedSet
|
- OrderedSet
|
||||||
- Reachability
|
- ReachabilitySwift
|
||||||
- SDWebImage
|
- SDWebImage
|
||||||
- Sentry
|
- Sentry
|
||||||
- SentryPrivate
|
- SentryPrivate
|
||||||
|
- sqlite3
|
||||||
- SwiftyGif
|
- SwiftyGif
|
||||||
- Toast
|
- Toast
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
connectivity:
|
app_links:
|
||||||
:path: ".symlinks/plugins/connectivity/ios"
|
:path: ".symlinks/plugins/app_links/ios"
|
||||||
|
connectivity_plus:
|
||||||
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
file_picker:
|
file_picker:
|
||||||
|
@ -164,18 +188,20 @@ EXTERNAL SOURCES:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_email_sender:
|
flutter_email_sender:
|
||||||
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
||||||
flutter_inappwebview:
|
flutter_inappwebview_ios:
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
|
flutter_local_authentication:
|
||||||
|
:path: ".symlinks/plugins/flutter_local_authentication/ios"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_secure_storage:
|
flutter_secure_storage:
|
||||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||||
flutter_sodium:
|
|
||||||
:path: ".symlinks/plugins/flutter_sodium/ios"
|
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
|
local_auth_darwin:
|
||||||
|
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||||
local_auth_ios:
|
local_auth_ios:
|
||||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||||
move_to_background:
|
move_to_background:
|
||||||
|
@ -194,50 +220,58 @@ EXTERNAL SOURCES:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
|
smart_auth:
|
||||||
|
:path: ".symlinks/plugins/smart_auth/ios"
|
||||||
|
sodium_libs:
|
||||||
|
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/darwin"
|
||||||
uni_links:
|
sqlite3_flutter_libs:
|
||||||
:path: ".symlinks/plugins/uni_links/ios"
|
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
|
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795
|
||||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||||
|
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||||
flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
|
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||||
|
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
|
||||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98
|
||||||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
||||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66
|
||||||
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
|
SDWebImage: 981fd7e860af070920f249fd092420006014c3eb
|
||||||
Sentry: e3203780941722a1fcfee99e351de14244c7f806
|
Sentry: ebc12276bd17613a114ab359074096b6b3725203
|
||||||
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
|
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e
|
||||||
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
|
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
|
||||||
|
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
|
||||||
|
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||||
|
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
|
||||||
|
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
|
||||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
|
||||||
|
|
||||||
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
|
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@
|
||||||
97C146E61CF9000F007C117D /* Project object */ = {
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 1430;
|
LastUpgradeCheck = 1510;
|
||||||
ORGANIZATIONNAME = "";
|
ORGANIZATIONNAME = "";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
97C146ED1CF9000F007C117D = {
|
97C146ED1CF9000F007C117D = {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1430"
|
LastUpgradeVersion = "1510"
|
||||||
version = "1.3">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import UIKit
|
|
||||||
import Flutter
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import app_links
|
||||||
|
|
||||||
@UIApplicationMain
|
@UIApplicationMain
|
||||||
@objc class AppDelegate: FlutterAppDelegate {
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
@ -8,6 +9,15 @@ import Flutter
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,5 +78,9 @@
|
||||||
</array>
|
</array>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -12,15 +12,18 @@ import 'package:ente_auth/locale.dart';
|
||||||
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
||||||
import 'package:ente_auth/services/update_service.dart';
|
import 'package:ente_auth/services/update_service.dart';
|
||||||
import 'package:ente_auth/services/user_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/home_page.dart';
|
||||||
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_localizations/flutter_localizations.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 {
|
class App extends StatefulWidget {
|
||||||
final Locale locale;
|
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) {
|
static void setLocale(BuildContext context, Locale newLocale) {
|
||||||
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
||||||
|
@ -31,7 +34,7 @@ class App extends StatefulWidget {
|
||||||
State<App> createState() => _AppState();
|
State<App> createState() => _AppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppState extends State<App> {
|
class _AppState extends State<App> with WindowListener, TrayListener {
|
||||||
late StreamSubscription<SignedOutEvent> _signedOutEvent;
|
late StreamSubscription<SignedOutEvent> _signedOutEvent;
|
||||||
late StreamSubscription<SignedInEvent> _signedInEvent;
|
late StreamSubscription<SignedInEvent> _signedInEvent;
|
||||||
Locale? locale;
|
Locale? locale;
|
||||||
|
@ -41,8 +44,19 @@ class _AppState extends State<App> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initWindowManager() async {
|
||||||
|
windowManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initTrayManager() async {
|
||||||
|
trayManager.addListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
initWindowManager();
|
||||||
|
initTrayManager();
|
||||||
|
|
||||||
_signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) {
|
_signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -76,6 +90,10 @@ class _AppState extends State<App> {
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
|
windowManager.removeListener(this);
|
||||||
|
trayManager.removeListener(this);
|
||||||
|
|
||||||
_signedOutEvent.cancel();
|
_signedOutEvent.cancel();
|
||||||
_signedInEvent.cancel();
|
_signedInEvent.cancel();
|
||||||
}
|
}
|
||||||
|
@ -134,4 +152,45 @@ class _AppState extends State<App> {
|
||||||
: const OnboardingPage(),
|
: 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
class AppRoute {
|
|
||||||
static const String enterSecretKeyPage = "enterSecretKeyPage";
|
|
||||||
static const String settings = "settings";
|
|
||||||
}
|
|
|
@ -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<Color>(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
if (states.contains(MaterialState.disabled)) {
|
|
||||||
return bgDisabled;
|
|
||||||
}
|
|
||||||
return bgEnabled;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
if (states.contains(MaterialState.disabled)) {
|
|
||||||
return fgDisabled;
|
|
||||||
}
|
|
||||||
return fgEnabled;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// ignore_for_file: deprecated_member_use
|
||||||
|
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
import "dart:developer";
|
import "dart:developer";
|
||||||
|
|
||||||
|
|
|
@ -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/key_gen_result.dart';
|
||||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||||
import 'package:ente_auth/store/authenticator_db.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_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
class Configuration {
|
class Configuration {
|
||||||
|
@ -72,9 +72,10 @@ class Configuration {
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
_preferences = await SharedPreferences.getInstance();
|
_preferences = await SharedPreferences.getInstance();
|
||||||
|
sqfliteFfiInit();
|
||||||
_secureStorage = const FlutterSecureStorage();
|
_secureStorage = const FlutterSecureStorage();
|
||||||
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
||||||
_tempDirectory = _documentsDirectory + "/temp/";
|
_tempDirectory = "$_documentsDirectory/temp/";
|
||||||
final tempDirectory = io.Directory(_tempDirectory);
|
final tempDirectory = io.Directory(_tempDirectory);
|
||||||
try {
|
try {
|
||||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||||
|
@ -162,7 +163,7 @@ class Configuration {
|
||||||
// decrypt the master key
|
// decrypt the master key
|
||||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||||
utf8.encode(password) as Uint8List,
|
utf8.encode(password),
|
||||||
kekSalt,
|
kekSalt,
|
||||||
);
|
);
|
||||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||||
|
@ -172,28 +173,28 @@ class Configuration {
|
||||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||||
|
|
||||||
// Generate a public-private keypair and encrypt the latter
|
// Generate a public-private keypair and encrypt the latter
|
||||||
final keyPair = await CryptoUtil.generateKeyPair();
|
final keyPair = CryptoUtil.generateKeyPair();
|
||||||
final encryptedSecretKeyData =
|
final encryptedSecretKeyData =
|
||||||
CryptoUtil.encryptSync(keyPair.sk, masterKey);
|
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||||
|
|
||||||
final attributes = KeyAttributes(
|
final attributes = KeyAttributes(
|
||||||
Sodium.bin2base64(kekSalt),
|
CryptoUtil.bin2base64(kekSalt),
|
||||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||||
Sodium.bin2base64(keyPair.pk),
|
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||||
Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
|
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||||
Sodium.bin2base64(encryptedSecretKeyData.nonce!),
|
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||||
derivedKeyResult.memLimit,
|
derivedKeyResult.memLimit,
|
||||||
derivedKeyResult.opsLimit,
|
derivedKeyResult.opsLimit,
|
||||||
Sodium.bin2base64(encryptedMasterKey.encryptedData!),
|
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||||
Sodium.bin2base64(encryptedMasterKey.nonce!),
|
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||||
Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
|
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||||
Sodium.bin2base64(encryptedRecoveryKey.nonce!),
|
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||||
);
|
);
|
||||||
final privateAttributes = PrivateKeyAttributes(
|
final privateAttributes = PrivateKeyAttributes(
|
||||||
Sodium.bin2base64(masterKey),
|
CryptoUtil.bin2base64(masterKey),
|
||||||
Sodium.bin2hex(recoveryKey),
|
CryptoUtil.bin2hex(recoveryKey),
|
||||||
Sodium.bin2base64(keyPair.sk),
|
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||||
);
|
);
|
||||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||||
}
|
}
|
||||||
|
@ -208,7 +209,7 @@ class Configuration {
|
||||||
// decrypt the master key
|
// decrypt the master key
|
||||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||||
utf8.encode(password) as Uint8List,
|
utf8.encode(password),
|
||||||
kekSalt,
|
kekSalt,
|
||||||
);
|
);
|
||||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||||
|
@ -220,9 +221,9 @@ class Configuration {
|
||||||
final existingAttributes = getKeyAttributes();
|
final existingAttributes = getKeyAttributes();
|
||||||
|
|
||||||
final updatedAttributes = existingAttributes!.copyWith(
|
final updatedAttributes = existingAttributes!.copyWith(
|
||||||
kekSalt: Sodium.bin2base64(kekSalt),
|
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||||
encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||||
keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||||
memLimit: derivedKeyResult.memLimit,
|
memLimit: derivedKeyResult.memLimit,
|
||||||
opsLimit: derivedKeyResult.opsLimit,
|
opsLimit: derivedKeyResult.opsLimit,
|
||||||
);
|
);
|
||||||
|
@ -240,8 +241,8 @@ class Configuration {
|
||||||
}) async {
|
}) async {
|
||||||
_logger.info('Start decryptAndSaveSecrets');
|
_logger.info('Start decryptAndSaveSecrets');
|
||||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||||
utf8.encode(password) as Uint8List,
|
utf8.encode(password),
|
||||||
Sodium.base642bin(attributes.kekSalt),
|
CryptoUtil.base642bin(attributes.kekSalt),
|
||||||
attributes.memLimit,
|
attributes.memLimit,
|
||||||
attributes.opsLimit,
|
attributes.opsLimit,
|
||||||
);
|
);
|
||||||
|
@ -250,31 +251,31 @@ class Configuration {
|
||||||
Uint8List key;
|
Uint8List key;
|
||||||
try {
|
try {
|
||||||
key = CryptoUtil.decryptSync(
|
key = CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(attributes.encryptedKey),
|
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||||
keyEncryptionKey,
|
keyEncryptionKey,
|
||||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe('master-key failed, incorrect password?', e);
|
_logger.severe('master-key failed, incorrect password?', e);
|
||||||
throw Exception("Incorrect password");
|
throw Exception("Incorrect password");
|
||||||
}
|
}
|
||||||
_logger.info("master-key done");
|
_logger.info("master-key done");
|
||||||
await setKey(Sodium.bin2base64(key));
|
await setKey(CryptoUtil.bin2base64(key));
|
||||||
final secretKey = CryptoUtil.decryptSync(
|
final secretKey = CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||||
key,
|
key,
|
||||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||||
);
|
);
|
||||||
_logger.info("secret-key done");
|
_logger.info("secret-key done");
|
||||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||||
final token = CryptoUtil.openSealSync(
|
final token = CryptoUtil.openSealSync(
|
||||||
Sodium.base642bin(getEncryptedToken()!),
|
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||||
Sodium.base642bin(attributes.publicKey),
|
CryptoUtil.base642bin(attributes.publicKey),
|
||||||
secretKey,
|
secretKey,
|
||||||
);
|
);
|
||||||
_logger.info('appToken done');
|
_logger.info('appToken done');
|
||||||
await setToken(
|
await setToken(
|
||||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||||
);
|
);
|
||||||
return keyEncryptionKey;
|
return keyEncryptionKey;
|
||||||
}
|
}
|
||||||
|
@ -293,28 +294,28 @@ class Configuration {
|
||||||
Uint8List masterKey;
|
Uint8List masterKey;
|
||||||
try {
|
try {
|
||||||
masterKey = await CryptoUtil.decrypt(
|
masterKey = await CryptoUtil.decrypt(
|
||||||
Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||||
Sodium.hex2bin(recoveryKey),
|
CryptoUtil.hex2bin(recoveryKey),
|
||||||
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
|
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_logger.severe(e);
|
_logger.severe(e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
await setKey(Sodium.bin2base64(masterKey));
|
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||||
final secretKey = CryptoUtil.decryptSync(
|
final secretKey = CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||||
masterKey,
|
masterKey,
|
||||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||||
);
|
);
|
||||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||||
final token = CryptoUtil.openSealSync(
|
final token = CryptoUtil.openSealSync(
|
||||||
Sodium.base642bin(getEncryptedToken()!),
|
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||||
Sodium.base642bin(attributes.publicKey),
|
CryptoUtil.base642bin(attributes.publicKey),
|
||||||
secretKey,
|
secretKey,
|
||||||
);
|
);
|
||||||
await setToken(
|
await setToken(
|
||||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,27 +408,31 @@ class Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? getKey() {
|
Uint8List? getKey() {
|
||||||
return _key == null ? null : Sodium.base642bin(_key!);
|
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? getSecretKey() {
|
Uint8List? getSecretKey() {
|
||||||
return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
|
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? getAuthSecretKey() {
|
Uint8List? getAuthSecretKey() {
|
||||||
return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
|
return _authSecretKey == null
|
||||||
|
? null
|
||||||
|
: CryptoUtil.base642bin(_authSecretKey!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List? getOfflineSecretKey() {
|
Uint8List? getOfflineSecretKey() {
|
||||||
return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
|
return _offlineAuthKey == null
|
||||||
|
? null
|
||||||
|
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List getRecoveryKey() {
|
Uint8List getRecoveryKey() {
|
||||||
final keyAttributes = getKeyAttributes()!;
|
final keyAttributes = getKeyAttributes()!;
|
||||||
return CryptoUtil.decryptSync(
|
return CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||||
getKey()!,
|
getKey()!,
|
||||||
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +459,7 @@ class Configuration {
|
||||||
iOptions: _secureStorageOptionsIOS,
|
iOptions: _secureStorageOptionsIOS,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
|
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
|
||||||
await _secureStorage.write(
|
await _secureStorage.write(
|
||||||
key: offlineAuthSecretKey,
|
key: offlineAuthSecretKey,
|
||||||
value: _offlineAuthKey,
|
value: _offlineAuthKey,
|
||||||
|
|
|
@ -7,7 +7,8 @@ const String sentryDSN =
|
||||||
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
|
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
|
||||||
const String sentryTunnel = "https://sentry-reporter.ente.io";
|
const String sentryTunnel = "https://sentry-reporter.ente.io";
|
||||||
const String roadmapURL = "https://roadmap.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 microSecondsInDay = 86400000000;
|
||||||
const int android11SDKINT = 30;
|
const int android11SDKINT = 30;
|
||||||
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
|
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
class InvalidFileError extends ArgumentError {
|
class InvalidFileError extends ArgumentError {
|
||||||
InvalidFileError(String message) : super(message);
|
InvalidFileError(String super.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidFileUploadState extends AssertionError {
|
class InvalidFileUploadState extends AssertionError {
|
||||||
InvalidFileUploadState(String message) : super(message);
|
InvalidFileUploadState(String super.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubscriptionAlreadyClaimedError extends Error {}
|
class SubscriptionAlreadyClaimedError extends Error {}
|
||||||
|
@ -30,19 +30,15 @@ class UnauthorizedError extends Error {}
|
||||||
class RequestCancelledError extends Error {}
|
class RequestCancelledError extends Error {}
|
||||||
|
|
||||||
class InvalidSyncStatusError extends AssertionError {
|
class InvalidSyncStatusError extends AssertionError {
|
||||||
InvalidSyncStatusError(String message) : super(message);
|
InvalidSyncStatusError(String super.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnauthorizedEditError extends AssertionError {}
|
class UnauthorizedEditError extends AssertionError {}
|
||||||
|
|
||||||
class InvalidStateError 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 SrpSetupNotCompleteError extends Error {}
|
||||||
|
|
||||||
class AuthenticatorKeyNotFound extends Error {}
|
class AuthenticatorKeyNotFound extends Error {}
|
||||||
|
|
|
@ -235,14 +235,14 @@ class SuperLogging {
|
||||||
extraLines = null;
|
extraLines = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
|
final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
|
||||||
|
|
||||||
// write to stdout
|
// write to stdout
|
||||||
printLog(str);
|
printLog(str);
|
||||||
|
|
||||||
// push to log queue
|
// push to log queue
|
||||||
if (fileIsEnabled) {
|
if (fileIsEnabled) {
|
||||||
fileQueueEntries.add(str + '\n');
|
fileQueueEntries.add('$str\n');
|
||||||
if (fileQueueEntries.length == 1) {
|
if (fileQueueEntries.length == 1) {
|
||||||
flushQueue();
|
flushQueue();
|
||||||
}
|
}
|
||||||
|
@ -275,7 +275,7 @@ class SuperLogging {
|
||||||
static var logChunkSize = 800;
|
static var logChunkSize = 800;
|
||||||
|
|
||||||
static void printLog(String text) {
|
static void printLog(String text) {
|
||||||
text.chunked(logChunkSize).forEach(print);
|
text.chunked(logChunkSize).forEach(debugPrint);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A queue to be consumed by [setupSentry].
|
/// A queue to be consumed by [setupSentry].
|
||||||
|
@ -354,7 +354,7 @@ class SuperLogging {
|
||||||
final date = config.dateFmt!.parse(basename(file.path));
|
final date = config.dateFmt!.parse(basename(file.path));
|
||||||
dates[file as File] = date;
|
dates[file as File] = date;
|
||||||
files.add(file);
|
files.add(file);
|
||||||
} on FormatException {}
|
} on Exception catch (_) {}
|
||||||
}
|
}
|
||||||
final nowTime = DateTime.now();
|
final nowTime = DateTime.now();
|
||||||
|
|
||||||
|
@ -374,7 +374,7 @@ class SuperLogging {
|
||||||
"deleting log file ${file.path}",
|
"deleting log file ${file.path}",
|
||||||
);
|
);
|
||||||
await file.delete();
|
await file.delete();
|
||||||
} catch (ignore) {}
|
} on Exception catch (_) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,10 @@ import 'package:dio/dio.dart';
|
||||||
import 'package:ente_auth/core/configuration.dart';
|
import 'package:ente_auth/core/configuration.dart';
|
||||||
import 'package:ente_auth/core/event_bus.dart';
|
import 'package:ente_auth/core/event_bus.dart';
|
||||||
import 'package:ente_auth/events/endpoint_updated_event.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:fk_user_agent/fk_user_agent.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
int kConnectTimeout = 15000;
|
int kConnectTimeout = 15000;
|
||||||
|
@ -16,17 +17,21 @@ class Network {
|
||||||
late Dio _enteDio;
|
late Dio _enteDio;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
await FkUserAgent.init();
|
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||||
final packageInfo = await PackageInfo.fromPlatform();
|
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||||
|
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||||
|
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||||
|
|
||||||
_dio = Dio(
|
_dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
connectTimeout: kConnectTimeout,
|
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||||
headers: {
|
headers: {
|
||||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||||
'X-Client-Version': packageInfo.version,
|
? FkUserAgent.userAgent
|
||||||
'X-Client-Package': packageInfo.packageName,
|
: Platform.operatingSystem,
|
||||||
|
'X-Client-Version': version,
|
||||||
|
'X-Client-Package': packageName,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -34,11 +39,14 @@ class Network {
|
||||||
_enteDio = Dio(
|
_enteDio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: endpoint,
|
baseUrl: endpoint,
|
||||||
connectTimeout: kConnectTimeout,
|
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||||
headers: {
|
headers: {
|
||||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
if (PlatformUtil.isMobile())
|
||||||
'X-Client-Version': packageInfo.version,
|
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||||
'X-Client-Package': packageInfo.packageName,
|
else
|
||||||
|
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||||
|
'X-Client-Version': version,
|
||||||
|
'X-Client-Package': packageName,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,12 +5,16 @@ import 'package:flutter/material.dart';
|
||||||
final lightThemeData = ThemeData(
|
final lightThemeData = ThemeData(
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
|
dividerTheme: const DividerThemeData(
|
||||||
|
color: Colors.black12,
|
||||||
|
),
|
||||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||||
primaryColor: const Color.fromRGBO(255, 110, 64, 1),
|
primaryColor: const Color.fromRGBO(255, 110, 64, 1),
|
||||||
primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541),
|
primaryColorLight: const Color.fromRGBO(0, 0, 0, 0.541),
|
||||||
iconTheme: const IconThemeData(color: Colors.black),
|
iconTheme: const IconThemeData(color: Colors.black),
|
||||||
primaryIconTheme:
|
primaryIconTheme:
|
||||||
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
||||||
|
buttonTheme: const ButtonThemeData(),
|
||||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||||
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
||||||
bgEnabled: const Color.fromRGBO(0, 0, 0, 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(255, 255, 255, 1)
|
||||||
: const Color.fromRGBO(0, 0, 0, 1);
|
: const Color.fromRGBO(0, 0, 0, 1);
|
||||||
}),
|
}),
|
||||||
), radioTheme: RadioThemeData(
|
),
|
||||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
radioTheme: RadioThemeData(
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
fillColor:
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
), switchTheme: SwitchThemeData(
|
),
|
||||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
switchTheme: SwitchThemeData(
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
thumbColor:
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
trackColor:
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
), colorScheme: const ColorScheme.light(
|
),
|
||||||
|
colorScheme: const ColorScheme.light(
|
||||||
primary: Colors.black,
|
primary: Colors.black,
|
||||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||||
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
||||||
|
@ -98,6 +120,9 @@ final lightThemeData = ThemeData(
|
||||||
final darkThemeData = ThemeData(
|
final darkThemeData = ThemeData(
|
||||||
fontFamily: 'Inter',
|
fontFamily: 'Inter',
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
|
dividerTheme: const DividerThemeData(
|
||||||
|
color: Colors.white12,
|
||||||
|
),
|
||||||
primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702),
|
primaryColorLight: const Color.fromRGBO(255, 255, 255, 0.702),
|
||||||
iconTheme: const IconThemeData(color: Colors.white),
|
iconTheme: const IconThemeData(color: Colors.white),
|
||||||
primaryIconTheme:
|
primaryIconTheme:
|
||||||
|
@ -105,6 +130,7 @@ final darkThemeData = ThemeData(
|
||||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||||
buttonTheme: const ButtonThemeData().copyWith(
|
buttonTheme: const ButtonThemeData().copyWith(
|
||||||
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
||||||
|
height: 56,
|
||||||
),
|
),
|
||||||
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
||||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||||
|
@ -164,24 +190,43 @@ final darkThemeData = ThemeData(
|
||||||
return const Color.fromRGBO(158, 158, 158, 1);
|
return const Color.fromRGBO(158, 158, 158, 1);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
), radioTheme: RadioThemeData(
|
),
|
||||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
radioTheme: RadioThemeData(
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
fillColor:
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
), switchTheme: SwitchThemeData(
|
),
|
||||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
switchTheme: SwitchThemeData(
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
thumbColor:
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
trackColor:
|
||||||
if (states.contains(MaterialState.disabled)) { return null; }
|
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
if (states.contains(MaterialState.disabled)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (states.contains(MaterialState.selected)) {
|
||||||
|
return const Color.fromRGBO(102, 187, 106, 1);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
),
|
||||||
|
colorScheme: const ColorScheme.dark(primary: Colors.white)
|
||||||
|
.copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||||
);
|
);
|
||||||
|
|
||||||
TextTheme _buildTextTheme(Color textColor) {
|
TextTheme _buildTextTheme(Color textColor) {
|
||||||
|
@ -400,6 +445,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
|
fixedSize: const Size.fromHeight(56),
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
|
@ -436,7 +482,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
||||||
}) {
|
}) {
|
||||||
return ElevatedButtonThemeData(
|
return ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
|
foregroundColor: onPrimary,
|
||||||
|
backgroundColor: primary,
|
||||||
|
elevation: elevation,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
textStyle: const TextStyle(
|
textStyle: const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|
|
@ -25,7 +25,7 @@ class AuthenticatorGateway {
|
||||||
try {
|
try {
|
||||||
final response = await _enteDio.get("/authenticator/key");
|
final response = await _enteDio.get("/authenticator/key");
|
||||||
return AuthKey.fromMap(response.data);
|
return AuthKey.fromMap(response.data);
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||||
throw AuthenticatorKeyNotFound();
|
throw AuthenticatorKeyNotFound();
|
||||||
} else {
|
} else {
|
||||||
|
@ -90,7 +90,7 @@ class AuthenticatorGateway {
|
||||||
}
|
}
|
||||||
return authEntities;
|
return authEntities;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is DioError && e.response?.statusCode == 401) {
|
if (e is DioException && e.response?.statusCode == 401) {
|
||||||
throw UnauthorizedError();
|
throw UnauthorizedError();
|
||||||
} else {
|
} else {
|
||||||
rethrow;
|
rethrow;
|
||||||
|
|
|
@ -145,6 +145,7 @@
|
||||||
"lostDeviceTitle": "Gerät verloren?",
|
"lostDeviceTitle": "Gerät verloren?",
|
||||||
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
|
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung",
|
||||||
"passkeyAuthTitle": "Passkey Authentifizierung",
|
"passkeyAuthTitle": "Passkey Authentifizierung",
|
||||||
|
"verifyPasskey": "Passkey verifizieren",
|
||||||
"recoverAccount": "Konto wiederherstellen",
|
"recoverAccount": "Konto wiederherstellen",
|
||||||
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
|
"enterRecoveryKeyHint": "Geben Sie Ihren Wiederherstellungsschlüssel ein",
|
||||||
"recover": "Wiederherstellen",
|
"recover": "Wiederherstellen",
|
||||||
|
@ -407,6 +408,7 @@
|
||||||
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
|
"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!",
|
"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...",
|
"waitingForBrowserRequest": "Warten auf Browseranfrage...",
|
||||||
|
"waitingForVerification": "Warte auf Bestätigung...",
|
||||||
"passkey": "Passkey",
|
"passkey": "Passkey",
|
||||||
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
|
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
|
||||||
"developerSettings": "Entwicklereinstellungen",
|
"developerSettings": "Entwicklereinstellungen",
|
||||||
|
|
|
@ -199,6 +199,10 @@
|
||||||
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
||||||
"doThisLater": "Do this later",
|
"doThisLater": "Do this later",
|
||||||
"saveKey": "Save key",
|
"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",
|
"back": "Back",
|
||||||
"createAccount": "Create account",
|
"createAccount": "Create account",
|
||||||
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
||||||
|
@ -407,6 +411,7 @@
|
||||||
"doNotSignOut": "Do not sign out",
|
"doNotSignOut": "Do not sign out",
|
||||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
"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!",
|
"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...",
|
"waitingForBrowserRequest": "Waiting for browser request...",
|
||||||
"waitingForVerification": "Waiting for verification...",
|
"waitingForVerification": "Waiting for verification...",
|
||||||
"passkey": "Passkey",
|
"passkey": "Passkey",
|
||||||
|
|
1
auth/lib/l10n/arb/app_ko.arb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -144,6 +144,8 @@
|
||||||
"enterCodeHint": "Voer de 6-cijferige code van je verificatie-app in",
|
"enterCodeHint": "Voer de 6-cijferige code van je verificatie-app in",
|
||||||
"lostDeviceTitle": "Apparaat verloren?",
|
"lostDeviceTitle": "Apparaat verloren?",
|
||||||
"twoFactorAuthTitle": "Tweestapsverificatie",
|
"twoFactorAuthTitle": "Tweestapsverificatie",
|
||||||
|
"passkeyAuthTitle": "Passkey verificatie",
|
||||||
|
"verifyPasskey": "Bevestig passkey",
|
||||||
"recoverAccount": "Account herstellen",
|
"recoverAccount": "Account herstellen",
|
||||||
"enterRecoveryKeyHint": "Voer je herstelsleutel in",
|
"enterRecoveryKeyHint": "Voer je herstelsleutel in",
|
||||||
"recover": "Herstellen",
|
"recover": "Herstellen",
|
||||||
|
@ -404,5 +406,15 @@
|
||||||
"signOutOtherDevices": "Afmelden bij andere apparaten",
|
"signOutOtherDevices": "Afmelden bij andere apparaten",
|
||||||
"doNotSignOut": "Niet uitloggen",
|
"doNotSignOut": "Niet uitloggen",
|
||||||
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
|
"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}"
|
||||||
}
|
}
|
|
@ -338,5 +338,22 @@
|
||||||
"deleteCodeAuthMessage": "Uwierzytelnij, aby usunąć kod",
|
"deleteCodeAuthMessage": "Uwierzytelnij, aby usunąć kod",
|
||||||
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR",
|
"showQRAuthMessage": "Uwierzytelnij, aby pokazać kod QR",
|
||||||
"confirmAccountDeleteTitle": "Potwierdź usunięcie konta",
|
"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"
|
||||||
}
|
}
|
|
@ -47,9 +47,9 @@
|
||||||
},
|
},
|
||||||
"copyEmailAction": "Copiar e-mail",
|
"copyEmailAction": "Copiar e-mail",
|
||||||
"exportLogsAction": "Exportar logs",
|
"exportLogsAction": "Exportar logs",
|
||||||
"reportABug": "Reportar um bug",
|
"reportABug": "Reportar um problema",
|
||||||
"crashAndErrorReporting": "Reporte de erros e falhas",
|
"crashAndErrorReporting": "Reporte de erros e falhas",
|
||||||
"reportBug": "Reportar bug",
|
"reportBug": "Reportar problema",
|
||||||
"emailUsMessage": "Por favor, envie um e-mail para {email}",
|
"emailUsMessage": "Por favor, envie um e-mail para {email}",
|
||||||
"@emailUsMessage": {
|
"@emailUsMessage": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -145,6 +145,7 @@
|
||||||
"lostDeviceTitle": "Perdeu seu dispositivo?",
|
"lostDeviceTitle": "Perdeu seu dispositivo?",
|
||||||
"twoFactorAuthTitle": "Autenticação de dois fatores",
|
"twoFactorAuthTitle": "Autenticação de dois fatores",
|
||||||
"passkeyAuthTitle": "Autenticação via Chave de acesso",
|
"passkeyAuthTitle": "Autenticação via Chave de acesso",
|
||||||
|
"verifyPasskey": "Verificar chave de acesso",
|
||||||
"recoverAccount": "Recuperar conta",
|
"recoverAccount": "Recuperar conta",
|
||||||
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
|
"enterRecoveryKeyHint": "Digite sua chave de recuperação",
|
||||||
"recover": "Recuperar",
|
"recover": "Recuperar",
|
||||||
|
@ -162,7 +163,7 @@
|
||||||
"invalidEmailMessage": "Por favor, insira um endereço de e-mail válido.",
|
"invalidEmailMessage": "Por favor, insira um endereço de e-mail válido.",
|
||||||
"deleteAccount": "Excluir conta",
|
"deleteAccount": "Excluir conta",
|
||||||
"deleteAccountQuery": "Sentiremos muito por vê-lo partir. Você está enfrentando algum problema?",
|
"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",
|
"noDeleteAccountAction": "Não, excluir conta",
|
||||||
"initiateAccountDeleteTitle": "Por favor, autentique-se para iniciar a exclusão de conta",
|
"initiateAccountDeleteTitle": "Por favor, autentique-se para iniciar a exclusão de conta",
|
||||||
"sendEmail": "Enviar e-mail",
|
"sendEmail": "Enviar e-mail",
|
||||||
|
@ -407,6 +408,7 @@
|
||||||
"hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)",
|
"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!",
|
"hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!",
|
||||||
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
|
"waitingForBrowserRequest": "Aguardando solicitação do navegador...",
|
||||||
|
"waitingForVerification": "Esperando por verificação...",
|
||||||
"passkey": "Chave de acesso",
|
"passkey": "Chave de acesso",
|
||||||
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
|
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
|
||||||
"developerSettings": "Configurações de desenvolvedor",
|
"developerSettings": "Configurações de desenvolvedor",
|
||||||
|
|
|
@ -145,6 +145,7 @@
|
||||||
"lostDeviceTitle": "丢失了设备吗?",
|
"lostDeviceTitle": "丢失了设备吗?",
|
||||||
"twoFactorAuthTitle": "双因素认证",
|
"twoFactorAuthTitle": "双因素认证",
|
||||||
"passkeyAuthTitle": "通行密钥认证",
|
"passkeyAuthTitle": "通行密钥认证",
|
||||||
|
"verifyPasskey": "验证通行密钥",
|
||||||
"recoverAccount": "恢复账户",
|
"recoverAccount": "恢复账户",
|
||||||
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
"enterRecoveryKeyHint": "输入您的恢复密钥",
|
||||||
"recover": "恢复",
|
"recover": "恢复",
|
||||||
|
@ -407,6 +408,7 @@
|
||||||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
|
||||||
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
"waitingForBrowserRequest": "正在等待浏览器请求...",
|
||||||
|
"waitingForVerification": "等待验证...",
|
||||||
"passkey": "通行密钥",
|
"passkey": "通行密钥",
|
||||||
"developerSettingsWarning": "您确定要修改开发者设置吗?",
|
"developerSettingsWarning": "您确定要修改开发者设置吗?",
|
||||||
"developerSettings": "开发者设置",
|
"developerSettings": "开发者设置",
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||||
import 'package:computer/computer.dart';
|
|
||||||
import "package:ente_auth/app/view/app.dart";
|
import "package:ente_auth/app/view/app.dart";
|
||||||
import 'package:ente_auth/core/configuration.dart';
|
import 'package:ente_auth/core/configuration.dart';
|
||||||
import 'package:ente_auth/core/constants.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/update_service.dart';
|
||||||
import 'package:ente_auth/services/user_remote_flag_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/user_service.dart';
|
||||||
|
import 'package:ente_auth/services/window_listener_service.dart';
|
||||||
import 'package:ente_auth/store/code_store.dart';
|
import 'package:ente_auth/store/code_store.dart';
|
||||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||||
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
||||||
import 'package:ente_auth/ui/utils/icon_utils.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/foundation.dart';
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
|
@ -29,11 +31,52 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:privacy_screen/privacy_screen.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");
|
final _logger = Logger("main");
|
||||||
|
|
||||||
|
Future<void> 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 {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
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 _runInForeground();
|
||||||
await _setupPrivacyScreen();
|
await _setupPrivacyScreen();
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
|
@ -70,10 +113,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||||
|
String dir = "";
|
||||||
|
try {
|
||||||
|
dir = "${(await getApplicationSupportDirectory()).path}/logs";
|
||||||
|
} catch (_) {}
|
||||||
await SuperLogging.main(
|
await SuperLogging.main(
|
||||||
LogConfig(
|
LogConfig(
|
||||||
body: function,
|
body: function,
|
||||||
logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
|
logDirPath: dir,
|
||||||
maxLogFiles: 5,
|
maxLogFiles: 5,
|
||||||
sentryDsn: sentryDSN,
|
sentryDsn: sentryDSN,
|
||||||
enableInDebugMode: true,
|
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<void> _init(bool bool, {String? via}) async {
|
Future<void> _init(bool bool, {String? via}) async {
|
||||||
// Start workers asynchronously. No need to wait for them to start
|
_registerWindowsProtocol();
|
||||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
|
await initCryptoUtil();
|
||||||
CryptoUtil.init();
|
|
||||||
await PreferenceService.instance.init();
|
await PreferenceService.instance.init();
|
||||||
await CodeStore.instance.init();
|
await CodeStore.instance.init();
|
||||||
await Configuration.instance.init();
|
await Configuration.instance.init();
|
||||||
|
@ -100,6 +156,7 @@ Future<void> _init(bool bool, {String? via}) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setupPrivacyScreen() async {
|
Future<void> _setupPrivacyScreen() async {
|
||||||
|
if (!PlatformUtil.isMobile()) return;
|
||||||
final brightness =
|
final brightness =
|
||||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||||
bool isInDarkMode = brightness == Brightness.dark;
|
bool isInDarkMode = brightness == Brightness.dark;
|
||||||
|
|
|
@ -57,14 +57,7 @@ class Code {
|
||||||
updatedAlgo,
|
updatedAlgo,
|
||||||
updatedType,
|
updatedType,
|
||||||
updatedCounter,
|
updatedCounter,
|
||||||
"otpauth://${updatedType.name}/" +
|
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
|
||||||
updateIssuer +
|
|
||||||
":" +
|
|
||||||
updateAccount +
|
|
||||||
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
|
|
||||||
updateIssuer +
|
|
||||||
"&period=$updatePeriod&secret=" +
|
|
||||||
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
|
|
||||||
generatedID: generatedID,
|
generatedID: generatedID,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -83,14 +76,7 @@ class Code {
|
||||||
Algorithm.sha1,
|
Algorithm.sha1,
|
||||||
Type.totp,
|
Type.totp,
|
||||||
0,
|
0,
|
||||||
"otpauth://totp/" +
|
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
|
||||||
issuer +
|
|
||||||
":" +
|
|
||||||
account +
|
|
||||||
"?algorithm=SHA1&digits=6&issuer=" +
|
|
||||||
issuer +
|
|
||||||
"&period=30&secret=" +
|
|
||||||
secret,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +127,7 @@ class Code {
|
||||||
if (uri.queryParameters.containsKey("issuer")) {
|
if (uri.queryParameters.containsKey("issuer")) {
|
||||||
String issuerName = uri.queryParameters['issuer']!;
|
String issuerName = uri.queryParameters['issuer']!;
|
||||||
// Handle issuer name with period
|
// 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=")) {
|
if (issuerName.contains("period=")) {
|
||||||
return issuerName.substring(0, issuerName.indexOf("period="));
|
return issuerName.substring(0, issuerName.indexOf("period="));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:ente_auth/app/view/app.dart';
|
import 'package:ente_auth/app/view/app.dart';
|
||||||
import 'package:ente_auth/core/configuration.dart';
|
import 'package:ente_auth/core/configuration.dart';
|
||||||
|
@ -28,7 +29,7 @@ import "package:flutter/material.dart";
|
||||||
import 'package:local_auth/local_auth.dart';
|
import 'package:local_auth/local_auth.dart';
|
||||||
|
|
||||||
class OnboardingPage extends StatefulWidget {
|
class OnboardingPage extends StatefulWidget {
|
||||||
const OnboardingPage({Key? key}) : super(key: key);
|
const OnboardingPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||||
|
@ -86,11 +87,16 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Center(
|
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints:
|
||||||
|
const BoxConstraints.tightFor(height: 800, width: 450),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
vertical: 40.0,
|
||||||
|
horizontal: 40,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
@ -143,8 +149,10 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
Text(
|
Text(
|
||||||
l10n.onBoardingBody,
|
l10n.onBoardingBody,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.titleLarge!.copyWith(
|
.textTheme
|
||||||
|
.titleLarge!
|
||||||
|
.copyWith(
|
||||||
color: Colors.white38,
|
color: Colors.white38,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -159,10 +167,11 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
text: l10n.newUser,
|
text: l10n.newUser,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 16),
|
||||||
Container(
|
Container(
|
||||||
|
height: 56,
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
child: Hero(
|
child: Hero(
|
||||||
tag: "log_in",
|
tag: "log_in",
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
|
@ -189,8 +198,9 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
child: Text(
|
child: Text(
|
||||||
l10n.useOffline,
|
l10n.useOffline,
|
||||||
style: body.copyWith(
|
style: body.copyWith(
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.mutedTextColor,
|
.colorScheme
|
||||||
|
.mutedTextColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -204,11 +214,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _optForOfflineMode() async {
|
Future<void> _optForOfflineMode() async {
|
||||||
bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
|
bool canCheckBio = Platform.isMacOS || Platform.isLinux
|
||||||
|
? true
|
||||||
|
: await LocalAuthentication().canCheckBiometrics;
|
||||||
if (!canCheckBio) {
|
if (!canCheckBio) {
|
||||||
showToast(
|
showToast(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
||||||
class SetupEnterSecretKeyPage extends StatefulWidget {
|
class SetupEnterSecretKeyPage extends StatefulWidget {
|
||||||
final Code? code;
|
final Code? code;
|
||||||
|
|
||||||
SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
|
SetupEnterSecretKeyPage({this.code, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SetupEnterSecretKeyPage> createState() =>
|
State<SetupEnterSecretKeyPage> createState() =>
|
||||||
|
@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||||
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
||||||
);
|
);
|
||||||
_secretController = TextEditingController(
|
_secretController = TextEditingController(
|
||||||
text: widget.code != null ? widget.code!.secret : null,
|
text: widget.code?.secret,
|
||||||
);
|
);
|
||||||
_secretKeyObscured = widget.code != null;
|
_secretKeyObscured = widget.code != null;
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(l10n.importAccountPageTitle),
|
title: Text(l10n.importAccountPageTitle),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: Center(
|
||||||
child: Center(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import "package:ente_auth/l10n/l10n.dart";
|
import "package:ente_auth/l10n/l10n.dart";
|
||||||
|
@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
||||||
class ViewQrPage extends StatelessWidget {
|
class ViewQrPage extends StatelessWidget {
|
||||||
final Code? code;
|
final Code? code;
|
||||||
|
|
||||||
ViewQrPage({this.code, Key? key}) : super(key: key);
|
ViewQrPage({this.code, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(l10n.qrCode),
|
title: Text(l10n.qrCode),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: Center(
|
||||||
child: Center(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
QrImage(
|
QrImageView(
|
||||||
data: code!.rawData,
|
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,
|
version: QrVersions.auto,
|
||||||
size: qrSize,
|
size: qrSize,
|
||||||
),
|
),
|
||||||
|
|
|
@ -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/models/authenticator/local_auth_entity.dart';
|
||||||
import 'package:ente_auth/store/authenticator_db.dart';
|
import 'package:ente_auth/store/authenticator_db.dart';
|
||||||
import 'package:ente_auth/store/offline_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/foundation.dart';
|
||||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -75,10 +74,10 @@ class AuthenticatorService {
|
||||||
final key = await getOrCreateAuthDataKey(mode);
|
final key = await getOrCreateAuthDataKey(mode);
|
||||||
for (LocalAuthEntity e in result) {
|
for (LocalAuthEntity e in result) {
|
||||||
try {
|
try {
|
||||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
final decryptedValue = await CryptoUtil.decryptData(
|
||||||
Sodium.base642bin(e.encryptedData),
|
CryptoUtil.base642bin(e.encryptedData),
|
||||||
key,
|
key,
|
||||||
Sodium.base642bin(e.header),
|
CryptoUtil.base642bin(e.header),
|
||||||
);
|
);
|
||||||
final hasSynced = !(e.id == null || e.shouldSync);
|
final hasSynced = !(e.id == null || e.shouldSync);
|
||||||
entities.add(
|
entities.add(
|
||||||
|
@ -101,12 +100,13 @@ class AuthenticatorService {
|
||||||
AccountMode accountMode,
|
AccountMode accountMode,
|
||||||
) async {
|
) async {
|
||||||
var key = await getOrCreateAuthDataKey(accountMode);
|
var key = await getOrCreateAuthDataKey(accountMode);
|
||||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||||
utf8.encode(plainText) as Uint8List,
|
utf8.encode(plainText),
|
||||||
key,
|
key,
|
||||||
);
|
);
|
||||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
String encryptedData =
|
||||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||||
|
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||||
final insertedID = accountMode.isOnline
|
final insertedID = accountMode.isOnline
|
||||||
? await _db.insert(encryptedData, header)
|
? await _db.insert(encryptedData, header)
|
||||||
: await _offlineDb.insert(encryptedData, header);
|
: await _offlineDb.insert(encryptedData, header);
|
||||||
|
@ -123,12 +123,13 @@ class AuthenticatorService {
|
||||||
AccountMode accountMode,
|
AccountMode accountMode,
|
||||||
) async {
|
) async {
|
||||||
var key = await getOrCreateAuthDataKey(accountMode);
|
var key = await getOrCreateAuthDataKey(accountMode);
|
||||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||||
utf8.encode(plainText) as Uint8List,
|
utf8.encode(plainText),
|
||||||
key,
|
key,
|
||||||
);
|
);
|
||||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
String encryptedData =
|
||||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||||
|
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||||
final int affectedRows = accountMode.isOnline
|
final int affectedRows = accountMode.isOnline
|
||||||
? await _db.updateEntry(generatedID, encryptedData, header)
|
? await _db.updateEntry(generatedID, encryptedData, header)
|
||||||
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
||||||
|
@ -191,25 +192,25 @@ class AuthenticatorService {
|
||||||
Future<void> _remoteToLocalSync() async {
|
Future<void> _remoteToLocalSync() async {
|
||||||
_logger.info('Initiating remote to local sync');
|
_logger.info('Initiating remote to local sync');
|
||||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||||
_logger.info("Current sync is " + lastSyncTime.toString());
|
_logger.info("Current sync is $lastSyncTime");
|
||||||
const int fetchLimit = 500;
|
const int fetchLimit = 500;
|
||||||
final List<AuthEntity> result =
|
final List<AuthEntity> result =
|
||||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
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) {
|
if (result.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||||
List<String> deletedIDs =
|
List<String> deletedIDs =
|
||||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
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);
|
result.removeWhere((element) => element.isDeleted);
|
||||||
await _db.insertOrReplace(result);
|
await _db.insertOrReplace(result);
|
||||||
if (deletedIDs.isNotEmpty) {
|
if (deletedIDs.isNotEmpty) {
|
||||||
await _db.deleteByIDs(ids: deletedIDs);
|
await _db.deleteByIDs(ids: deletedIDs);
|
||||||
}
|
}
|
||||||
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
await _prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
_logger.info("Setting synctime to $maxSyncTime");
|
||||||
if (result.length == fetchLimit) {
|
if (result.length == fetchLimit) {
|
||||||
_logger.info("Diff limit reached, pulling again");
|
_logger.info("Diff limit reached, pulling again");
|
||||||
await _remoteToLocalSync();
|
await _remoteToLocalSync();
|
||||||
|
@ -223,7 +224,7 @@ class AuthenticatorService {
|
||||||
.where((element) => element.shouldSync || element.id == null)
|
.where((element) => element.shouldSync || element.id == null)
|
||||||
.toList();
|
.toList();
|
||||||
_logger.info(
|
_logger.info(
|
||||||
pendingUpdate.length.toString() + " entries to be updated at remote",
|
"${pendingUpdate.length} entries to be updated at remote",
|
||||||
);
|
);
|
||||||
for (LocalAuthEntity entity in pendingUpdate) {
|
for (LocalAuthEntity entity in pendingUpdate) {
|
||||||
if (entity.id == null) {
|
if (entity.id == null) {
|
||||||
|
@ -262,21 +263,21 @@ class AuthenticatorService {
|
||||||
try {
|
try {
|
||||||
final AuthKey response = await _gateway.getKey();
|
final AuthKey response = await _gateway.getKey();
|
||||||
final authKey = CryptoUtil.decryptSync(
|
final authKey = CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(response.encryptedKey),
|
CryptoUtil.base642bin(response.encryptedKey),
|
||||||
_config.getKey()!,
|
_config.getKey()!,
|
||||||
Sodium.base642bin(response.header),
|
CryptoUtil.base642bin(response.header),
|
||||||
);
|
);
|
||||||
await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
|
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
|
||||||
return authKey;
|
return authKey;
|
||||||
} on AuthenticatorKeyNotFound catch (e) {
|
} on AuthenticatorKeyNotFound catch (e) {
|
||||||
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
||||||
final key = CryptoUtil.generateKey();
|
final key = CryptoUtil.generateKey();
|
||||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||||
await _gateway.createKey(
|
await _gateway.createKey(
|
||||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||||
);
|
);
|
||||||
await _config.setAuthSecretKey(Sodium.bin2base64(key));
|
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
|
||||||
return key;
|
return key;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
||||||
|
|
|
@ -50,7 +50,7 @@ class BillingService {
|
||||||
|
|
||||||
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
||||||
return _dio.get(
|
return _dio.get(
|
||||||
_config.getHttpEndpoint() + "/billing/user-plans/",
|
"${_config.getHttpEndpoint()}/billing/user-plans/",
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth-Token": _config.getToken(),
|
"X-Auth-Token": _config.getToken(),
|
||||||
|
@ -60,7 +60,7 @@ class BillingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
||||||
return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
|
return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Subscription> verifySubscription(
|
Future<Subscription> verifySubscription(
|
||||||
|
@ -70,7 +70,7 @@ class BillingService {
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/billing/verify-subscription",
|
"${_config.getHttpEndpoint()}/billing/verify-subscription",
|
||||||
data: {
|
data: {
|
||||||
"paymentProvider": paymentProvider ??
|
"paymentProvider": paymentProvider ??
|
||||||
(Platform.isAndroid ? "playstore" : "appstore"),
|
(Platform.isAndroid ? "playstore" : "appstore"),
|
||||||
|
@ -84,7 +84,7 @@ class BillingService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Subscription.fromMap(response.data["subscription"]);
|
return Subscription.fromMap(response.data["subscription"]);
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response != null && e.response!.statusCode == 409) {
|
if (e.response != null && e.response!.statusCode == 409) {
|
||||||
throw SubscriptionAlreadyClaimedError();
|
throw SubscriptionAlreadyClaimedError();
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +100,7 @@ class BillingService {
|
||||||
if (_cachedSubscription == null) {
|
if (_cachedSubscription == null) {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_config.getHttpEndpoint() + "/billing/subscription",
|
"${_config.getHttpEndpoint()}/billing/subscription",
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth-Token": _config.getToken(),
|
"X-Auth-Token": _config.getToken(),
|
||||||
|
@ -109,7 +109,7 @@ class BillingService {
|
||||||
);
|
);
|
||||||
_cachedSubscription =
|
_cachedSubscription =
|
||||||
Subscription.fromMap(response.data["subscription"]);
|
Subscription.fromMap(response.data["subscription"]);
|
||||||
} on DioError catch (e, s) {
|
} on DioException catch (e, s) {
|
||||||
_logger.severe(e, s);
|
_logger.severe(e, s);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +120,7 @@ class BillingService {
|
||||||
Future<Subscription> cancelStripeSubscription() async {
|
Future<Subscription> cancelStripeSubscription() async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
|
"${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth-Token": _config.getToken(),
|
"X-Auth-Token": _config.getToken(),
|
||||||
|
@ -129,7 +129,7 @@ class BillingService {
|
||||||
);
|
);
|
||||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||||
return subscription;
|
return subscription;
|
||||||
} on DioError catch (e, s) {
|
} on DioException catch (e, s) {
|
||||||
_logger.severe(e, s);
|
_logger.severe(e, s);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,7 @@ class BillingService {
|
||||||
Future<Subscription> activateStripeSubscription() async {
|
Future<Subscription> activateStripeSubscription() async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
|
"${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {
|
headers: {
|
||||||
"X-Auth-Token": _config.getToken(),
|
"X-Auth-Token": _config.getToken(),
|
||||||
|
@ -147,7 +147,7 @@ class BillingService {
|
||||||
);
|
);
|
||||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||||
return subscription;
|
return subscription;
|
||||||
} on DioError catch (e, s) {
|
} on DioException catch (e, s) {
|
||||||
_logger.severe(e, s);
|
_logger.severe(e, s);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ class BillingService {
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",
|
"${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
"redirectURL": kWebPaymentRedirectUrl,
|
"redirectURL": kWebPaymentRedirectUrl,
|
||||||
},
|
},
|
||||||
|
@ -169,7 +169,7 @@ class BillingService {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return response.data["url"];
|
return response.data["url"];
|
||||||
} on DioError catch (e, s) {
|
} on DioException catch (e, s) {
|
||||||
_logger.severe(e, s);
|
_logger.severe(e, s);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:ente_auth/core/configuration.dart';
|
import 'package:ente_auth/core/configuration.dart';
|
||||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||||
import 'package:ente_auth/utils/auth_util.dart';
|
import 'package:ente_auth/utils/auth_util.dart';
|
||||||
import 'package:ente_auth/utils/dialog_util.dart';
|
import 'package:ente_auth/utils/dialog_util.dart';
|
||||||
import 'package:ente_auth/utils/toast_util.dart';
|
import 'package:ente_auth/utils/toast_util.dart';
|
||||||
import 'package:flutter/material.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:local_auth/local_auth.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class LocalAuthenticationService {
|
class LocalAuthenticationService {
|
||||||
LocalAuthenticationService._privateConstructor();
|
LocalAuthenticationService._privateConstructor();
|
||||||
static final LocalAuthenticationService instance =
|
static final LocalAuthenticationService instance =
|
||||||
LocalAuthenticationService._privateConstructor();
|
LocalAuthenticationService._privateConstructor();
|
||||||
|
final logger = Logger((LocalAuthenticationService).toString());
|
||||||
|
|
||||||
Future<bool> requestLocalAuthentication(
|
Future<bool> requestLocalAuthentication(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
@ -38,7 +44,7 @@ class LocalAuthenticationService {
|
||||||
String errorDialogContent, [
|
String errorDialogContent, [
|
||||||
String errorDialogTitle = "",
|
String errorDialogTitle = "",
|
||||||
]) async {
|
]) async {
|
||||||
if (await LocalAuthentication().isDeviceSupported()) {
|
if (await _isLocalAuthSupportedOnDevice()) {
|
||||||
AppLock.of(context)!.disable();
|
AppLock.of(context)!.disable();
|
||||||
final result = await requestAuthentication(
|
final result = await requestAuthentication(
|
||||||
context,
|
context,
|
||||||
|
@ -65,6 +71,12 @@ class LocalAuthenticationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
||||||
return await LocalAuthentication().isDeviceSupported();
|
try {
|
||||||
|
return Platform.isMacOS || Platform.isLinux
|
||||||
|
? await FlutterLocalAuthentication().canAuthenticate()
|
||||||
|
: await LocalAuthentication().isDeviceSupported();
|
||||||
|
} on MissingPluginException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,7 @@ class NotificationService {
|
||||||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>();
|
AndroidFlutterLocalNotificationsPlugin>();
|
||||||
if (implementation != null) {
|
if (implementation != null) {
|
||||||
// ignore: unawaited_futures
|
await implementation.requestNotificationsPermission();
|
||||||
implementation.requestPermission();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||||
import 'package:ente_auth/core/constants.dart';
|
import 'package:ente_auth/core/constants.dart';
|
||||||
import 'package:ente_auth/core/network.dart';
|
import 'package:ente_auth/core/network.dart';
|
||||||
import 'package:ente_auth/services/notification_service.dart';
|
import 'package:ente_auth/services/notification_service.dart';
|
||||||
|
import 'package:ente_auth/utils/platform_util.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
@ -130,7 +131,8 @@ class UpdateService {
|
||||||
|
|
||||||
bool isIndependent() {
|
bool isIndependent() {
|
||||||
return flavor == "independent" ||
|
return flavor == "independent" ||
|
||||||
_packageInfo.packageName.endsWith("independent");
|
_packageInfo.packageName.endsWith("independent") ||
|
||||||
|
PlatformUtil.isDesktop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,6 +143,7 @@ class LatestVersionInfo {
|
||||||
final bool? shouldForceUpdate;
|
final bool? shouldForceUpdate;
|
||||||
final int lastSupportedVersionCode;
|
final int lastSupportedVersionCode;
|
||||||
final String? url;
|
final String? url;
|
||||||
|
final String? release;
|
||||||
final int? size;
|
final int? size;
|
||||||
final bool? shouldNotify;
|
final bool? shouldNotify;
|
||||||
|
|
||||||
|
@ -151,6 +154,7 @@ class LatestVersionInfo {
|
||||||
this.shouldForceUpdate,
|
this.shouldForceUpdate,
|
||||||
this.lastSupportedVersionCode,
|
this.lastSupportedVersionCode,
|
||||||
this.url,
|
this.url,
|
||||||
|
this.release,
|
||||||
this.size,
|
this.size,
|
||||||
this.shouldNotify,
|
this.shouldNotify,
|
||||||
);
|
);
|
||||||
|
@ -163,6 +167,7 @@ class LatestVersionInfo {
|
||||||
map['shouldForceUpdate'],
|
map['shouldForceUpdate'],
|
||||||
map['lastSupportedVersionCode'] ?? 1,
|
map['lastSupportedVersionCode'] ?? 1,
|
||||||
map['url'],
|
map['url'],
|
||||||
|
map['release'],
|
||||||
map['size'],
|
map['size'],
|
||||||
map['shouldNotify'],
|
map['shouldNotify'],
|
||||||
);
|
);
|
||||||
|
|
|
@ -96,7 +96,7 @@ class UserRemoteFlagService {
|
||||||
queryParams["defaultValue"] = defaultValue;
|
queryParams["defaultValue"] = defaultValue;
|
||||||
}
|
}
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_config.getHttpEndpoint() + "/remote-store",
|
"${_config.getHttpEndpoint()}/remote-store",
|
||||||
queryParameters: queryParams,
|
queryParameters: queryParams,
|
||||||
options: Options(
|
options: Options(
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -119,7 +119,7 @@ class UserRemoteFlagService {
|
||||||
Future<void> _updateKeyValue(String key, String value) async {
|
Future<void> _updateKeyValue(String key, String value) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/remote-store/update",
|
"${_config.getHttpEndpoint()}/remote-store/update",
|
||||||
data: {
|
data: {
|
||||||
"key": key,
|
"key": key,
|
||||||
"value": value,
|
"value": value,
|
||||||
|
|
|
@ -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/passkey_page.dart';
|
||||||
import 'package:ente_auth/ui/two_factor_authentication_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/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/dialog_util.dart';
|
||||||
import 'package:ente_auth/utils/toast_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/foundation.dart";
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
@ -80,7 +80,7 @@ class UserService {
|
||||||
await dialog.show();
|
await dialog.show();
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/ott",
|
"${_config.getHttpEndpoint()}/users/ott",
|
||||||
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
|
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
|
||||||
);
|
);
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
|
@ -102,7 +102,7 @@ class UserService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
unawaited(showGenericErrorDialog(context: context));
|
unawaited(showGenericErrorDialog(context: context));
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
_logger.info(e);
|
_logger.info(e);
|
||||||
if (e.response != null && e.response!.statusCode == 403) {
|
if (e.response != null && e.response!.statusCode == 403) {
|
||||||
|
@ -129,7 +129,7 @@ class UserService {
|
||||||
String type = "SubCancellation",
|
String type = "SubCancellation",
|
||||||
}) async {
|
}) async {
|
||||||
await _dio.post(
|
await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/anonymous/feedback",
|
"${_config.getHttpEndpoint()}/anonymous/feedback",
|
||||||
data: {"feedback": feedback, "type": "type"},
|
data: {"feedback": feedback, "type": "type"},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ class UserService {
|
||||||
try {
|
try {
|
||||||
final response = await _enteDio.get("/users/sessions");
|
final response = await _enteDio.get("/users/sessions");
|
||||||
return Sessions.fromMap(response.data);
|
return Sessions.fromMap(response.data);
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
_logger.info(e);
|
_logger.info(e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ class UserService {
|
||||||
"token": token,
|
"token": token,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
_logger.info(e);
|
_logger.info(e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,7 @@ class UserService {
|
||||||
Future<void> leaveFamilyPlan() async {
|
Future<void> leaveFamilyPlan() async {
|
||||||
try {
|
try {
|
||||||
await _enteDio.delete("/family/leave");
|
await _enteDio.delete("/family/leave");
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
_logger.warning('failed to leave family plan', e);
|
_logger.warning('failed to leave family plan', e);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
|
@ -306,11 +306,11 @@ class UserService {
|
||||||
"ott": ott,
|
"ott": ott,
|
||||||
};
|
};
|
||||||
if (!_config.isLoggedIn()) {
|
if (!_config.isLoggedIn()) {
|
||||||
verifyData["source"] = 'auth:' + _getRefSource();
|
verifyData["source"] = 'auth:${_getRefSource()}';
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/verify-email",
|
"${_config.getHttpEndpoint()}/users/verify-email",
|
||||||
data: verifyData,
|
data: verifyData,
|
||||||
);
|
);
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
|
@ -346,7 +346,7 @@ class UserService {
|
||||||
// should never reach here
|
// should never reach here
|
||||||
throw Exception("unexpected response during email verification");
|
throw Exception("unexpected response during email verification");
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
_logger.info(e);
|
_logger.info(e);
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
if (e.response != null && e.response!.statusCode == 410) {
|
if (e.response != null && e.response!.statusCode == 410) {
|
||||||
|
@ -410,7 +410,7 @@ class UserService {
|
||||||
context.l10n.oops,
|
context.l10n.oops,
|
||||||
context.l10n.verificationFailedPleaseTryAgain,
|
context.l10n.verificationFailedPleaseTryAgain,
|
||||||
);
|
);
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
if (e.response != null && e.response!.statusCode == 403) {
|
if (e.response != null && e.response!.statusCode == 403) {
|
||||||
// ignore: unawaited_futures
|
// ignore: unawaited_futures
|
||||||
|
@ -460,7 +460,7 @@ class UserService {
|
||||||
Future<SrpAttributes> getSrpAttributes(String email) async {
|
Future<SrpAttributes> getSrpAttributes(String email) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_config.getHttpEndpoint() + "/users/srp/attributes",
|
"${_config.getHttpEndpoint()}/users/srp/attributes",
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
"email": email,
|
"email": email,
|
||||||
},
|
},
|
||||||
|
@ -470,7 +470,7 @@ class UserService {
|
||||||
} else {
|
} else {
|
||||||
throw Exception("get-srp-attributes action failed");
|
throw Exception("get-srp-attributes action failed");
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
if (e.response != null && e.response!.statusCode == 404) {
|
if (e.response != null && e.response!.statusCode == 404) {
|
||||||
throw SrpSetupNotCompleteError();
|
throw SrpSetupNotCompleteError();
|
||||||
}
|
}
|
||||||
|
@ -523,7 +523,7 @@ class UserService {
|
||||||
// ignore: need to calculate secret to get M1, unused_local_variable
|
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||||
final clientS = client.calculateSecret(serverB);
|
final clientS = client.calculateSecret(serverB);
|
||||||
final clientM = client.calculateClientEvidenceMessage();
|
final clientM = client.calculateClientEvidenceMessage();
|
||||||
// ignore: unused_local_variable
|
|
||||||
late Response _;
|
late Response _;
|
||||||
if (setKeysRequest == null) {
|
if (setKeysRequest == null) {
|
||||||
_ = await _enteDio.post(
|
_ = await _enteDio.post(
|
||||||
|
@ -573,7 +573,7 @@ class UserService {
|
||||||
late Uint8List keyEncryptionKey;
|
late Uint8List keyEncryptionKey;
|
||||||
_logger.finest('Start deriving key');
|
_logger.finest('Start deriving key');
|
||||||
keyEncryptionKey = await CryptoUtil.deriveKey(
|
keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||||
utf8.encode(userPassword) as Uint8List,
|
utf8.encode(userPassword),
|
||||||
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
||||||
srpAttributes.memLimit,
|
srpAttributes.memLimit,
|
||||||
srpAttributes.opsLimit,
|
srpAttributes.opsLimit,
|
||||||
|
@ -596,7 +596,7 @@ class UserService {
|
||||||
|
|
||||||
final A = client.generateClientCredentials(salt, identity, password);
|
final A = client.generateClientCredentials(salt, identity, password);
|
||||||
final createSessionResponse = await _dio.post(
|
final createSessionResponse = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/srp/create-session",
|
"${_config.getHttpEndpoint()}/users/srp/create-session",
|
||||||
data: {
|
data: {
|
||||||
"srpUserID": srpAttributes.srpUserID,
|
"srpUserID": srpAttributes.srpUserID,
|
||||||
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||||
|
@ -610,7 +610,7 @@ class UserService {
|
||||||
final clientS = client.calculateSecret(serverB);
|
final clientS = client.calculateSecret(serverB);
|
||||||
final clientM = client.calculateClientEvidenceMessage();
|
final clientM = client.calculateClientEvidenceMessage();
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/srp/verify-session",
|
"${_config.getHttpEndpoint()}/users/srp/verify-session",
|
||||||
data: {
|
data: {
|
||||||
"sessionID": sessionID,
|
"sessionID": sessionID,
|
||||||
"srpUserID": srpAttributes.srpUserID,
|
"srpUserID": srpAttributes.srpUserID,
|
||||||
|
@ -709,7 +709,7 @@ class UserService {
|
||||||
await dialog.show();
|
await dialog.show();
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/two-factor/verify",
|
"${_config.getHttpEndpoint()}/users/two-factor/verify",
|
||||||
data: {
|
data: {
|
||||||
"sessionID": sessionID,
|
"sessionID": sessionID,
|
||||||
"code": code,
|
"code": code,
|
||||||
|
@ -729,7 +729,7 @@ class UserService {
|
||||||
(route) => route.isFirst,
|
(route) => route.isFirst,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
_logger.severe(e);
|
_logger.severe(e);
|
||||||
if (e.response != null && e.response!.statusCode == 404) {
|
if (e.response != null && e.response!.statusCode == 404) {
|
||||||
|
@ -772,7 +772,7 @@ class UserService {
|
||||||
await dialog.show();
|
await dialog.show();
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
"${_config.getHttpEndpoint()}/users/two-factor/recover",
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
"sessionID": sessionID,
|
"sessionID": sessionID,
|
||||||
"twoFactorType": twoFactorTypeToString(type),
|
"twoFactorType": twoFactorTypeToString(type),
|
||||||
|
@ -794,7 +794,7 @@ class UserService {
|
||||||
(route) => route.isFirst,
|
(route) => route.isFirst,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
_logger.severe(e);
|
_logger.severe(e);
|
||||||
if (e.response != null && e.response!.statusCode == 404) {
|
if (e.response != null && e.response!.statusCode == 404) {
|
||||||
|
@ -868,7 +868,7 @@ class UserService {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
final response = await _dio.post(
|
final response = await _dio.post(
|
||||||
_config.getHttpEndpoint() + "/users/two-factor/remove",
|
"${_config.getHttpEndpoint()}/users/two-factor/remove",
|
||||||
data: {
|
data: {
|
||||||
"sessionID": sessionID,
|
"sessionID": sessionID,
|
||||||
"secret": secret,
|
"secret": secret,
|
||||||
|
@ -891,7 +891,7 @@ class UserService {
|
||||||
(route) => route.isFirst,
|
(route) => route.isFirst,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} on DioError catch (e) {
|
} on DioException catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
_logger.severe(e);
|
_logger.severe(e);
|
||||||
if (e.response != null && e.response!.statusCode == 404) {
|
if (e.response != null && e.response!.statusCode == 404) {
|
||||||
|
|
36
auth/lib/services/window_listener_service.dart
Normal file
|
@ -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<void> 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<void> 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,10 +3,12 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||||
import 'package:ente_auth/models/authenticator/local_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:flutter/foundation.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
|
||||||
class AuthenticatorDB {
|
class AuthenticatorDB {
|
||||||
static const _databaseName = "ente.authenticator.db";
|
static const _databaseName = "ente.authenticator.db";
|
||||||
|
@ -25,6 +27,16 @@ class AuthenticatorDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Database> _initDatabase() async {
|
Future<Database> _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 =
|
final Directory documentsDirectory =
|
||||||
await getApplicationDocumentsDirectory();
|
await getApplicationDocumentsDirectory();
|
||||||
final String path = join(documentsDirectory.path, _databaseName);
|
final String path = join(documentsDirectory.path, _databaseName);
|
||||||
|
@ -166,7 +178,7 @@ class AuthenticatorDB {
|
||||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batch.commit();
|
final _ = await batch.commit();
|
||||||
debugPrint("Done");
|
debugPrint("Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,11 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||||
import 'package:ente_auth/models/authenticator/local_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:flutter/foundation.dart';
|
||||||
import 'package:path/path.dart';
|
import 'package:path/path.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:sqflite/sqflite.dart';
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
|
||||||
class OfflineAuthenticatorDB {
|
class OfflineAuthenticatorDB {
|
||||||
static const _databaseName = "ente.offline_authenticator.db";
|
static const _databaseName = "ente.offline_authenticator.db";
|
||||||
|
@ -26,6 +27,16 @@ class OfflineAuthenticatorDB {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Database> _initDatabase() async {
|
Future<Database> _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 =
|
final Directory documentsDirectory =
|
||||||
await getApplicationDocumentsDirectory();
|
await getApplicationDocumentsDirectory();
|
||||||
final String path = join(documentsDirectory.path, _databaseName);
|
final String path = join(documentsDirectory.path, _databaseName);
|
||||||
|
@ -152,7 +163,7 @@ class OfflineAuthenticatorDB {
|
||||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await batch.commit();
|
final _ = await batch.commit();
|
||||||
debugPrint("Done");
|
debugPrint("Done");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
||||||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||||
const Color _warning800 = Color(0xFFF53434);
|
const Color _warning800 = Color(0xFFF53434);
|
||||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||||
|
// ignore: unused_element
|
||||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||||
|
|
||||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class ChangeEmailDialog extends StatefulWidget {
|
class ChangeEmailDialog extends StatefulWidget {
|
||||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
const ChangeEmailDialog({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||||
|
|
|
@ -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/services/user_service.dart';
|
||||||
import 'package:ente_auth/ui/common/dialogs.dart';
|
import 'package:ente_auth/ui/common/dialogs.dart';
|
||||||
import 'package:ente_auth/ui/common/gradient_button.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/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/material.dart';
|
||||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
|
||||||
|
|
||||||
class DeleteAccountPage extends StatelessWidget {
|
class DeleteAccountPage extends StatelessWidget {
|
||||||
const DeleteAccountPage({
|
const DeleteAccountPage({
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
|
||||||
l10n.initiateAccountDeleteTitle,
|
l10n.initiateAccountDeleteTitle,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await PlatformUtil.refocusWindows();
|
||||||
|
|
||||||
if (hasAuthenticated) {
|
if (hasAuthenticated) {
|
||||||
final choice = await showChoiceDialogOld(
|
final choice = await showChoiceDialogOld(
|
||||||
context,
|
context,
|
||||||
|
@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final decryptChallenge = CryptoUtil.openSealSync(
|
final decryptChallenge = CryptoUtil.openSealSync(
|
||||||
Sodium.base642bin(response.encryptedChallenge),
|
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||||
Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
|
CryptoUtil.base642bin(
|
||||||
|
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||||
|
),
|
||||||
Configuration.instance.getSecretKey()!,
|
Configuration.instance.getSecretKey()!,
|
||||||
);
|
);
|
||||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:ente_auth/l10n/l10n.dart';
|
||||||
import 'package:ente_auth/services/user_service.dart';
|
import 'package:ente_auth/services/user_service.dart';
|
||||||
import 'package:ente_auth/theme/ente_theme.dart';
|
import 'package:ente_auth/theme/ente_theme.dart';
|
||||||
import 'package:ente_auth/ui/common/dynamic_fab.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:ente_auth/utils/toast_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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";
|
import "package:styled_text/styled_text.dart";
|
||||||
|
|
||||||
class EmailEntryPage extends StatefulWidget {
|
class EmailEntryPage extends StatefulWidget {
|
||||||
const EmailEntryPage({Key? key}) : super(key: key);
|
const EmailEntryPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||||
|
@ -190,6 +190,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
|
textInputAction: TextInputAction.next,
|
||||||
controller: _passwordController1,
|
controller: _passwordController1,
|
||||||
obscureText: !_password1Visible,
|
obscureText: !_password1Visible,
|
||||||
enableSuggestions: true,
|
enableSuggestions: true,
|
||||||
|
@ -427,15 +428,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||||
tags: {
|
tags: {
|
||||||
'u-terms': StyledTextActionTag(
|
'u-terms': StyledTextActionTag(
|
||||||
(String? text, Map<String?, String?> attrs) =>
|
(String? text, Map<String?, String?> attrs) =>
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.termsOfServicesTitle,
|
context.l10n.termsOfServicesTitle,
|
||||||
"https://ente.io/terms",
|
"https://ente.io/terms",
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
@ -443,15 +439,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||||
),
|
),
|
||||||
'u-policy': StyledTextActionTag(
|
'u-policy': StyledTextActionTag(
|
||||||
(String? text, Map<String?, String?> attrs) =>
|
(String? text, Map<String?, String?> attrs) =>
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.privacyPolicyTitle,
|
context.l10n.privacyPolicyTitle,
|
||||||
"https://ente.io/privacy",
|
"https://ente.io/privacy",
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
@ -494,15 +485,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||||
tags: {
|
tags: {
|
||||||
'underline': StyledTextActionTag(
|
'underline': StyledTextActionTag(
|
||||||
(String? text, Map<String?, String?> attrs) =>
|
(String? text, Map<String?, String?> attrs) =>
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.encryption,
|
context.l10n.encryption,
|
||||||
"https://ente.io/architecture",
|
"https://ente.io/architecture",
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
|
|
@ -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/services/user_service.dart';
|
||||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.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/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:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import "package:styled_text/styled_text.dart";
|
import "package:styled_text/styled_text.dart";
|
||||||
|
|
||||||
class LoginPage extends StatefulWidget {
|
class LoginPage extends StatefulWidget {
|
||||||
const LoginPage({Key? key}) : super(key: key);
|
const LoginPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LoginPage> createState() => _LoginPageState();
|
State<LoginPage> createState() => _LoginPageState();
|
||||||
|
@ -25,6 +25,36 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
Color? _emailInputFieldColor;
|
Color? _emailInputFieldColor;
|
||||||
final Logger _logger = Logger('_LoginPageState');
|
final Logger _logger = Logger('_LoginPageState');
|
||||||
|
|
||||||
|
Future<void> 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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_email = _config.getEmail();
|
_email = _config.getEmail();
|
||||||
|
@ -60,36 +90,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
isKeypadOpen: isKeypadOpen,
|
isKeypadOpen: isKeypadOpen,
|
||||||
isFormValid: _emailIsValid,
|
isFormValid: _emailIsValid,
|
||||||
buttonText: context.l10n.logInLabel,
|
buttonText: context.l10n.logInLabel,
|
||||||
onPressedFunction: () async {
|
onPressedFunction: onPressed,
|
||||||
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();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: fabLocation(),
|
floatingActionButtonLocation: fabLocation(),
|
||||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||||
|
@ -116,6 +117,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
autofillHints: const [AutofillHints.email],
|
autofillHints: const [AutofillHints.email],
|
||||||
|
onFieldSubmitted:
|
||||||
|
_emailIsValid ? (value) => onPressed() : null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: _emailInputFieldColor,
|
fillColor: _emailInputFieldColor,
|
||||||
filled: true,
|
filled: true,
|
||||||
|
@ -179,15 +182,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
tags: {
|
tags: {
|
||||||
'u-terms': StyledTextActionTag(
|
'u-terms': StyledTextActionTag(
|
||||||
(String? text, Map<String?, String?> attrs) =>
|
(String? text, Map<String?, String?> attrs) =>
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.termsOfServicesTitle,
|
context.l10n.termsOfServicesTitle,
|
||||||
"https://ente.io/terms",
|
"https://ente.io/terms",
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
@ -195,15 +193,10 @@ class _LoginPageState extends State<LoginPage> {
|
||||||
),
|
),
|
||||||
'u-policy': StyledTextActionTag(
|
'u-policy': StyledTextActionTag(
|
||||||
(String? text, Map<String?, String?> attrs) =>
|
(String? text, Map<String?, String?> attrs) =>
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.privacyPolicyTitle,
|
context.l10n.privacyPolicyTitle,
|
||||||
"https://ente.io/privacy",
|
"https://ente.io/privacy",
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import "package:dio/dio.dart";
|
import "package:dio/dio.dart";
|
||||||
import 'package:ente_auth/core/configuration.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/l10n/l10n.dart";
|
||||||
import "package:ente_auth/models/api/user/srp.dart";
|
import "package:ente_auth/models/api/user/srp.dart";
|
||||||
import "package:ente_auth/services/user_service.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/ui/components/buttons/button_widget.dart";
|
||||||
import "package:ente_auth/utils/dialog_util.dart";
|
import "package:ente_auth/utils/dialog_util.dart";
|
||||||
import "package:ente_auth/utils/email_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:flutter/material.dart';
|
||||||
import "package:logging/logging.dart";
|
import "package:logging/logging.dart";
|
||||||
|
|
||||||
|
@ -19,8 +19,7 @@ import "package:logging/logging.dart";
|
||||||
// volatile password.
|
// volatile password.
|
||||||
class LoginPasswordVerificationPage extends StatefulWidget {
|
class LoginPasswordVerificationPage extends StatefulWidget {
|
||||||
final SrpAttributes srpAttributes;
|
final SrpAttributes srpAttributes;
|
||||||
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
|
const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LoginPasswordVerificationPage> createState() =>
|
State<LoginPasswordVerificationPage> createState() =>
|
||||||
|
@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
|
||||||
bool _passwordInFocus = false;
|
bool _passwordInFocus = false;
|
||||||
bool _passwordVisible = false;
|
bool _passwordVisible = false;
|
||||||
|
|
||||||
|
Future<void> onPressed() async {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
await verifyPassword(context, _passwordController.text);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState
|
||||||
isKeypadOpen: isKeypadOpen,
|
isKeypadOpen: isKeypadOpen,
|
||||||
isFormValid: _passwordController.text.isNotEmpty,
|
isFormValid: _passwordController.text.isNotEmpty,
|
||||||
buttonText: context.l10n.logInLabel,
|
buttonText: context.l10n.logInLabel,
|
||||||
onPressedFunction: () async {
|
onPressedFunction: onPressed,
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
await verifyPassword(context, _passwordController.text);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: fabLocation(),
|
floatingActionButtonLocation: fabLocation(),
|
||||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||||
|
@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState
|
||||||
password,
|
password,
|
||||||
dialog,
|
dialog,
|
||||||
);
|
);
|
||||||
} on DioError catch (e, s) {
|
} on DioException catch (e, s) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
if (e.response != null && e.response!.statusCode == 401) {
|
if (e.response != null && e.response!.statusCode == 401) {
|
||||||
_logger.severe('server reject, failed verify SRP login', e, s);
|
_logger.severe('server reject, failed verify SRP login', e, s);
|
||||||
|
@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_logger.severe('API failure during SRP login', e, s);
|
_logger.severe('API failure during SRP login', e, s);
|
||||||
if (e.type == DioErrorType.other) {
|
if (e.type == DioExceptionType.unknown) {
|
||||||
await _showContactSupportDialog(
|
await _showContactSupportDialog(
|
||||||
context,
|
context,
|
||||||
context.l10n.noInternetConnection,
|
context.l10n.noInternetConnection,
|
||||||
|
@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
|
onFieldSubmitted: _passwordController.text.isNotEmpty
|
||||||
|
? (_) => onPressed()
|
||||||
|
: null,
|
||||||
key: const ValueKey("passwordInputField"),
|
key: const ValueKey("passwordInputField"),
|
||||||
autofillHints: const [AutofillHints.password],
|
autofillHints: const [AutofillHints.password],
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
|
|
@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
|
||||||
this.isChangeEmail = false,
|
this.isChangeEmail = false,
|
||||||
this.isCreateAccountScreen = false,
|
this.isCreateAccountScreen = false,
|
||||||
this.isResetPasswordScreen = false,
|
this.isResetPasswordScreen = false,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
||||||
|
@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
|
||||||
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||||
final _verificationCodeController = TextEditingController();
|
final _verificationCodeController = TextEditingController();
|
||||||
|
|
||||||
|
Future<void> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
|
@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||||
body: _getBody(),
|
body: _getBody(),
|
||||||
floatingActionButton: DynamicFAB(
|
floatingActionButton: DynamicFAB(
|
||||||
isKeypadOpen: isKeypadOpen,
|
isKeypadOpen: isKeypadOpen,
|
||||||
isFormValid: !(_verificationCodeController.text.isEmpty),
|
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||||
buttonText: l10n.verify,
|
buttonText: l10n.verify,
|
||||||
onPressedFunction: () {
|
onPressedFunction: onPressed,
|
||||||
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();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: fabLocation(),
|
floatingActionButtonLocation: fabLocation(),
|
||||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||||
|
@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
onFieldSubmitted: _verificationCodeController.text.isNotEmpty
|
||||||
|
? (_) => onPressed()
|
||||||
|
: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
filled: true,
|
filled: true,
|
||||||
hintText: l10n.tapToEnterCode,
|
hintText: l10n.tapToEnterCode,
|
||||||
|
|
|
@ -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/services/user_service.dart';
|
||||||
import 'package:ente_auth/ui/account/recovery_key_page.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/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/components/models/button_type.dart';
|
||||||
import 'package:ente_auth/ui/home_page.dart';
|
import 'package:ente_auth/ui/home_page.dart';
|
||||||
import 'package:ente_auth/utils/dialog_util.dart';
|
import 'package:ente_auth/utils/dialog_util.dart';
|
||||||
import 'package:ente_auth/utils/navigation_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_auth/utils/toast_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -25,7 +25,7 @@ enum PasswordEntryMode {
|
||||||
class PasswordEntryPage extends StatefulWidget {
|
class PasswordEntryPage extends StatefulWidget {
|
||||||
final PasswordEntryMode mode;
|
final PasswordEntryMode mode;
|
||||||
|
|
||||||
const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
|
const PasswordEntryPage({required this.mode, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||||
|
@ -149,11 +149,15 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: AutofillGroup(
|
child: AutofillGroup(
|
||||||
|
child: FocusTraversalGroup(
|
||||||
|
policy: OrderedTraversalPolicy(),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
vertical: 30,
|
||||||
|
horizontal: 20,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
buttonTextAndHeading,
|
buttonTextAndHeading,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
@ -211,6 +215,12 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
autofillHints: const [AutofillHints.newPassword],
|
autofillHints: const [AutofillHints.newPassword],
|
||||||
|
onFieldSubmitted: (_) {
|
||||||
|
do {
|
||||||
|
FocusScope.of(context).nextFocus();
|
||||||
|
} while (FocusScope.of(context).focusedChild!.context ==
|
||||||
|
null);
|
||||||
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor:
|
fillColor:
|
||||||
_isPasswordValid ? _validFieldValueColor : null,
|
_isPasswordValid ? _validFieldValueColor : null,
|
||||||
|
@ -255,9 +265,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
onChanged: (password) {
|
onChanged: (password) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_passwordInInputBox = password;
|
_passwordInInputBox = password;
|
||||||
_passwordStrength = estimatePasswordStrength(password);
|
_passwordStrength =
|
||||||
_isPasswordValid =
|
estimatePasswordStrength(password);
|
||||||
_passwordStrength >= kMildPasswordStrengthThreshold;
|
_isPasswordValid = _passwordStrength >=
|
||||||
|
kMildPasswordStrengthThreshold;
|
||||||
_passwordsMatch = _passwordInInputBox ==
|
_passwordsMatch = _passwordInInputBox ==
|
||||||
_passwordInInputConfirmationBox;
|
_passwordInInputConfirmationBox;
|
||||||
});
|
});
|
||||||
|
@ -274,9 +285,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
controller: _passwordController2,
|
controller: _passwordController2,
|
||||||
obscureText: !_password2Visible,
|
obscureText: !_password2Visible,
|
||||||
autofillHints: const [AutofillHints.newPassword],
|
autofillHints: const [AutofillHints.newPassword],
|
||||||
onEditingComplete: () => TextInput.finishAutofillContext(),
|
onEditingComplete: () =>
|
||||||
|
TextInput.finishAutofillContext(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: _passwordsMatch ? _validFieldValueColor : null,
|
fillColor:
|
||||||
|
_passwordsMatch ? _validFieldValueColor : null,
|
||||||
filled: true,
|
filled: true,
|
||||||
hintText: context.l10n.confirmPassword,
|
hintText: context.l10n.confirmPassword,
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
@ -326,11 +339,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity:
|
opacity: (_passwordInInputBox != '') && _password1InFocus
|
||||||
(_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
|
? 1
|
||||||
|
: 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding: const EdgeInsets.symmetric(
|
||||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
horizontal: 20,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.passwordStrength(passwordStrengthText),
|
context.l10n.passwordStrength(passwordStrengthText),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -343,17 +359,12 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push(
|
PlatformUtil.openWebView(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) {
|
|
||||||
return WebPage(
|
|
||||||
context.l10n.howItWorks,
|
context.l10n.howItWorks,
|
||||||
"https://ente.io/architecture",
|
"https://ente.io/architecture",
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: RichText(
|
child: RichText(
|
||||||
|
@ -373,6 +384,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -463,6 +475,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||||
showGenericErrorDialog(context: context);
|
showGenericErrorDialog(context: context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: unawaited_futures
|
// ignore: unawaited_futures
|
||||||
routeToPage(
|
routeToPage(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -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/common/dynamic_fab.dart';
|
||||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||||
import 'package:ente_auth/ui/home_page.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/dialog_util.dart';
|
||||||
import 'package:ente_auth/utils/email_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:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class PasswordReentryPage extends StatefulWidget {
|
class PasswordReentryPage extends StatefulWidget {
|
||||||
const PasswordReentryPage({Key? key}) : super(key: key);
|
const PasswordReentryPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||||
|
@ -261,8 +261,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Wrap(
|
child: Row(
|
||||||
alignment: WrapAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
|
@ -275,15 +275,19 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.forgotPassword,
|
context.l10n.forgotPassword,
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
@ -297,15 +301,19 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.popUntil((route) => route.isFirst);
|
.popUntil((route) => route.isFirst);
|
||||||
},
|
},
|
||||||
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
context.l10n.changeEmail,
|
context.l10n.changeEmail,
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
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/ente_theme_data.dart';
|
||||||
import 'package:ente_auth/l10n/l10n.dart';
|
import 'package:ente_auth/l10n/l10n.dart';
|
||||||
import 'package:ente_auth/ui/common/gradient_button.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:ente_auth/utils/toast_util.dart';
|
||||||
|
import 'package:file_saver/file_saver.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||||
const RecoveryKeyPage(
|
const RecoveryKeyPage(
|
||||||
this.recoveryKey,
|
this.recoveryKey,
|
||||||
this.doneText, {
|
this.doneText, {
|
||||||
Key? key,
|
super.key,
|
||||||
this.showAppBar,
|
this.showAppBar,
|
||||||
this.onDone,
|
this.onDone,
|
||||||
this.isDismissible,
|
this.isDismissible,
|
||||||
|
@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||||
this.text,
|
this.text,
|
||||||
this.subText,
|
this.subText,
|
||||||
this.showProgressBar = false,
|
this.showProgressBar = false,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||||
|
@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
bool _hasTriedToSave = false;
|
bool _hasTriedToSave = false;
|
||||||
final _recoveryKeyFile = io.File(
|
final _recoveryKeyFile = io.File(
|
||||||
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
|
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
? 32
|
? 32
|
||||||
: 120;
|
: 120;
|
||||||
|
|
||||||
|
Future<void> copy() async {
|
||||||
|
await Clipboard.setData(
|
||||||
|
ClipboardData(
|
||||||
|
text: recoveryKey,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
showShortToast(
|
||||||
|
context,
|
||||||
|
context.l10n.recoveryKeyCopiedToClipboard,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_hasTriedToSave = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: widget.showProgressBar
|
appBar: widget.showProgressBar
|
||||||
? AppBar(
|
? AppBar(
|
||||||
|
@ -113,65 +132,76 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||||
DottedBorder(
|
Container(
|
||||||
color: const Color.fromARGB(255, 105, 17, 127),
|
padding: const EdgeInsets.all(1),
|
||||||
//color of dotted/dash line
|
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,
|
strokeWidth: 1,
|
||||||
//thickness of dash/dots
|
color: const Color(0xFF6B6B6B),
|
||||||
dashPattern: const [6, 6],
|
dashPattern: const [6, 6],
|
||||||
radius: const Radius.circular(8),
|
radius: const Radius.circular(8),
|
||||||
//dash patterns, 10 is dash width, 6 is space width
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
//inner container
|
width: double.infinity,
|
||||||
// height: 120, //height of inner container
|
child: Stack(
|
||||||
width: double
|
|
||||||
.infinity, //width to 100% match to parent container.
|
|
||||||
// ignore: prefer_const_literals_to_create_immutables
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
Column(
|
||||||
onTap: () async {
|
children: [
|
||||||
await Clipboard.setData(
|
Builder(
|
||||||
ClipboardData(text: recoveryKey),
|
builder: (context) {
|
||||||
);
|
final content = Container(
|
||||||
showShortToast(
|
|
||||||
context,
|
|
||||||
context.l10n.recoveryKeyCopiedToClipboard,
|
|
||||||
);
|
|
||||||
setState(() {
|
|
||||||
_hasTriedToSave = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: const Color.fromRGBO(
|
|
||||||
49,
|
|
||||||
155,
|
|
||||||
86,
|
|
||||||
.2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
borderRadius: const BorderRadius.all(
|
|
||||||
Radius.circular(2),
|
|
||||||
),
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.recoveryKeyBoxColor,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: Text(
|
child: Text(
|
||||||
recoveryKey,
|
recoveryKey,
|
||||||
style:
|
textAlign: TextAlign.justify,
|
||||||
Theme.of(context).textTheme.bodyLarge,
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
child: PlatformCopy(
|
||||||
|
onPressed: copy,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
), // columnEnds
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -207,13 +237,16 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
final List<Widget> childrens = [];
|
final List<Widget> childrens = [];
|
||||||
if (!_hasTriedToSave) {
|
if (!_hasTriedToSave) {
|
||||||
childrens.add(
|
childrens.add(
|
||||||
ElevatedButton(
|
SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton(
|
||||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _saveKeys();
|
await _saveKeys();
|
||||||
},
|
},
|
||||||
child: Text(context.l10n.doThisLater),
|
child: Text(context.l10n.doThisLater),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
childrens.add(const SizedBox(height: 10));
|
childrens.add(const SizedBox(height: 10));
|
||||||
}
|
}
|
||||||
|
@ -221,31 +254,67 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
childrens.add(
|
childrens.add(
|
||||||
GradientButton(
|
GradientButton(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
await shareDialog(
|
||||||
|
context,
|
||||||
|
context.l10n.recoveryKey,
|
||||||
|
saveAction: () async {
|
||||||
|
await _saveRecoveryKey(recoveryKey);
|
||||||
|
},
|
||||||
|
sendAction: () async {
|
||||||
await _shareRecoveryKey(recoveryKey);
|
await _shareRecoveryKey(recoveryKey);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
text: context.l10n.saveKey,
|
text: context.l10n.saveKey,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_hasTriedToSave) {
|
if (_hasTriedToSave) {
|
||||||
childrens.add(const SizedBox(height: 10));
|
childrens.add(const SizedBox(height: 10));
|
||||||
childrens.add(
|
childrens.add(
|
||||||
ElevatedButton(
|
SizedBox(
|
||||||
|
height: 56,
|
||||||
|
child: ElevatedButton(
|
||||||
child: Text(widget.doneText),
|
child: Text(widget.doneText),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _saveKeys();
|
await _saveKeys();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
childrens.add(const SizedBox(height: 12));
|
childrens.add(const SizedBox(height: 12));
|
||||||
return childrens;
|
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 {
|
Future _shareRecoveryKey(String recoveryKey) async {
|
||||||
if (_recoveryKeyFile.existsSync()) {
|
if (_recoveryKeyFile.existsSync()) {
|
||||||
await _recoveryKeyFile.delete();
|
await _recoveryKeyFile.delete();
|
||||||
}
|
}
|
||||||
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
||||||
|
// ignore: deprecated_member_use
|
||||||
await Share.shareFiles([_recoveryKeyFile.path]);
|
await Share.shareFiles([_recoveryKeyFile.path]);
|
||||||
Future.delayed(const Duration(milliseconds: 500), () {
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||||
widget.onDone!();
|
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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:ente_auth/core/configuration.dart';
|
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/account/password_entry_page.dart';
|
||||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||||
import 'package:ente_auth/utils/dialog_util.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';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class RecoveryPage extends StatefulWidget {
|
class RecoveryPage extends StatefulWidget {
|
||||||
const RecoveryPage({Key? key}) : super(key: key);
|
const RecoveryPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||||
|
@ -19,6 +16,36 @@ class RecoveryPage extends StatefulWidget {
|
||||||
class _RecoveryPageState extends State<RecoveryPage> {
|
class _RecoveryPageState extends State<RecoveryPage> {
|
||||||
final _recoveryKey = TextEditingController();
|
final _recoveryKey = TextEditingController();
|
||||||
|
|
||||||
|
Future<void> 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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||||
|
@ -46,37 +73,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||||
isKeypadOpen: isKeypadOpen,
|
isKeypadOpen: isKeypadOpen,
|
||||||
isFormValid: _recoveryKey.text.isNotEmpty,
|
isFormValid: _recoveryKey.text.isNotEmpty,
|
||||||
buttonText: 'Recover',
|
buttonText: 'Recover',
|
||||||
onPressedFunction: () async {
|
onPressedFunction: onPressed,
|
||||||
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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: fabLocation(),
|
floatingActionButtonLocation: fabLocation(),
|
||||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||||
|
@ -89,7 +86,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Forgot password',
|
context.l10n.forgotPassword,
|
||||||
style: Theme.of(context).textTheme.headlineMedium,
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -140,9 +137,11 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"No recovery key?",
|
context.l10n.noRecoveryKeyTitle,
|
||||||
style:
|
style: Theme.of(context)
|
||||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
.textTheme
|
||||||
|
.titleMedium!
|
||||||
|
.copyWith(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
|
||||||
import "package:ente_auth/l10n/l10n.dart";
|
import "package:ente_auth/l10n/l10n.dart";
|
||||||
import "package:ente_auth/theme/ente_theme.dart";
|
import "package:ente_auth/theme/ente_theme.dart";
|
||||||
import 'package:ente_auth/ui/common/dynamic_fab.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_auth/utils/dialog_util.dart";
|
||||||
|
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import "package:flutter_sodium/flutter_sodium.dart";
|
|
||||||
import "package:logging/logging.dart";
|
import "package:logging/logging.dart";
|
||||||
|
|
||||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||||
|
@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
|
||||||
final OnPasswordVerifiedFn onPasswordVerified;
|
final OnPasswordVerifiedFn onPasswordVerified;
|
||||||
final Function? onPasswordError;
|
final Function? onPasswordError;
|
||||||
|
|
||||||
const RequestPasswordVerificationPage(
|
const RequestPasswordVerificationPage({
|
||||||
{super.key, required this.onPasswordVerified, this.onPasswordError,});
|
super.key,
|
||||||
|
required this.onPasswordVerified,
|
||||||
|
this.onPasswordError,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RequestPasswordVerificationPage> createState() =>
|
State<RequestPasswordVerificationPage> createState() =>
|
||||||
|
@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
|
||||||
try {
|
try {
|
||||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||||
utf8.encode(_passwordController.text) as Uint8List,
|
utf8.encode(_passwordController.text),
|
||||||
Sodium.base642bin(attributes.kekSalt),
|
CryptoUtil.base642bin(attributes.kekSalt),
|
||||||
attributes.memLimit,
|
attributes.memLimit,
|
||||||
attributes.opsLimit,
|
attributes.opsLimit,
|
||||||
);
|
);
|
||||||
CryptoUtil.decryptSync(
|
CryptoUtil.decryptSync(
|
||||||
Sodium.base642bin(attributes.encryptedKey),
|
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||||
keyEncryptionKey,
|
keyEncryptionKey,
|
||||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||||
);
|
);
|
||||||
await dialog.show();
|
await dialog.show();
|
||||||
// pop
|
// pop
|
||||||
|
|
|
@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class SessionsPage extends StatefulWidget {
|
class SessionsPage extends StatefulWidget {
|
||||||
const SessionsPage({Key? key}) : super(key: key);
|
const SessionsPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SessionsPage> createState() => _SessionsPageState();
|
State<SessionsPage> createState() => _SessionsPageState();
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
import 'package:bip39/bip39.dart' as bip39;
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:ente_auth/core/configuration.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/ui/components/buttons/button_widget.dart';
|
||||||
import 'package:ente_auth/utils/dialog_util.dart';
|
import 'package:ente_auth/utils/dialog_util.dart';
|
||||||
import 'package:ente_auth/utils/navigation_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/material.dart';
|
||||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class VerifyRecoveryPage extends StatefulWidget {
|
class VerifyRecoveryPage extends StatefulWidget {
|
||||||
const VerifyRecoveryPage({Key? key}) : super(key: key);
|
const VerifyRecoveryPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
||||||
|
@ -34,14 +33,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||||
try {
|
try {
|
||||||
final String inputKey = _recoveryKey.text.trim();
|
final String inputKey = _recoveryKey.text.trim();
|
||||||
final String recoveryKey =
|
final String recoveryKey =
|
||||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||||
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
||||||
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
||||||
try {
|
try {
|
||||||
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await dialog.hide();
|
await dialog.hide();
|
||||||
if (e is DioError && e.type == DioErrorType.other) {
|
if (e is DioException && e.type == DioExceptionType.unknown) {
|
||||||
await showErrorDialog(
|
await showErrorDialog(
|
||||||
context,
|
context,
|
||||||
"No internet connection",
|
"No internet connection",
|
||||||
|
@ -88,12 +87,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||||
context,
|
context,
|
||||||
"Please authenticate to view your recovery key",
|
"Please authenticate to view your recovery key",
|
||||||
);
|
);
|
||||||
|
await PlatformUtil.refocusWindows();
|
||||||
|
|
||||||
if (hasAuthenticated) {
|
if (hasAuthenticated) {
|
||||||
String recoveryKey;
|
String recoveryKey;
|
||||||
try {
|
try {
|
||||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
recoveryKey =
|
||||||
// ignore: unawaited_futures
|
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||||
routeToPage(
|
await routeToPage(
|
||||||
context,
|
context,
|
||||||
RecoveryKeyPage(
|
RecoveryKeyPage(
|
||||||
recoveryKey,
|
recoveryKey,
|
||||||
|
|
|
@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
|
||||||
final int period;
|
final int period;
|
||||||
|
|
||||||
CodeTimerProgress({
|
CodeTimerProgress({
|
||||||
Key? key,
|
super.key,
|
||||||
required this.period,
|
required this.period,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_CodeTimerProgressState createState() => _CodeTimerProgressState();
|
State createState() => _CodeTimerProgressState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
||||||
|
|
|
@ -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/code_timer_progress.dart';
|
||||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||||
import 'package:ente_auth/utils/dialog_util.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/toast_util.dart';
|
||||||
import 'package:ente_auth/utils/totp_util.dart';
|
import 'package:ente_auth/utils/totp_util.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:move_to_background/move_to_background.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 {
|
class CodeWidget extends StatefulWidget {
|
||||||
final Code code;
|
final Code code;
|
||||||
|
|
||||||
const CodeWidget(this.code, {Key? key}) : super(key: key);
|
const CodeWidget(this.code, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CodeWidget> createState() => _CodeWidgetState();
|
State<CodeWidget> createState() => _CodeWidgetState();
|
||||||
|
@ -84,7 +86,37 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
|
margin: const EdgeInsets.only(left: 16, right: 16, bottom: 8, top: 8),
|
||||||
child: Slidable(
|
child: Builder(
|
||||||
|
builder: (context) {
|
||||||
|
if (PlatformUtil.isDesktop()) {
|
||||||
|
return ContextMenuRegion(
|
||||||
|
contextMenu: ContextMenu(
|
||||||
|
entries: <ContextMenuEntry>[
|
||||||
|
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),
|
key: ValueKey(widget.code.hashCode),
|
||||||
endActionPane: ActionPane(
|
endActionPane: ActionPane(
|
||||||
extentRatio: 0.60,
|
extentRatio: 0.60,
|
||||||
|
@ -133,7 +165,17 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: Builder(
|
||||||
|
builder: (context) => _clippedCard(l10n),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _clippedCard(AppLocalizations l10n) {
|
||||||
|
return ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
|
||||||
|
@ -162,8 +204,6 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -373,9 +413,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onEditPressed(_) async {
|
Future<void> _onEditPressed(_) async {
|
||||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||||
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
||||||
if (!_isAuthSuccessful) {
|
await PlatformUtil.refocusWindows();
|
||||||
|
if (!isAuthSuccessful) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Code? code = await Navigator.of(context).push(
|
final Code? code = await Navigator.of(context).push(
|
||||||
|
@ -391,9 +432,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onShowQrPressed(_) async {
|
Future<void> _onShowQrPressed(_) async {
|
||||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||||
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
||||||
if (!_isAuthSuccessful) {
|
await PlatformUtil.refocusWindows();
|
||||||
|
if (!isAuthSuccessful) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ignore: unused_local_variable
|
// ignore: unused_local_variable
|
||||||
|
@ -407,14 +449,15 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDeletePressed(_) async {
|
void _onDeletePressed(_) async {
|
||||||
bool _isAuthSuccessful =
|
bool isAuthSuccessful =
|
||||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||||
context,
|
context,
|
||||||
context.l10n.deleteCodeAuthMessage,
|
context.l10n.deleteCodeAuthMessage,
|
||||||
);
|
);
|
||||||
if (!_isAuthSuccessful) {
|
if (!isAuthSuccessful) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
FocusScope.of(context).requestFocus();
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
await showChoiceActionSheet(
|
await showChoiceActionSheet(
|
||||||
context,
|
context,
|
||||||
|
@ -451,7 +494,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||||
code = code.replaceAll(RegExp(r'\d'), '•');
|
code = code.replaceAll(RegExp(r'\d'), '•');
|
||||||
}
|
}
|
||||||
if (code.length == 6) {
|
if (code.length == 6) {
|
||||||
return code.substring(0, 3) + " " + code.substring(3, 6);
|
return "${code.substring(0, 3)} ${code.substring(3, 6)}";
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
class BottomShadowWidget extends StatelessWidget {
|
class BottomShadowWidget extends StatelessWidget {
|
||||||
final double offsetDy;
|
final double offsetDy;
|
||||||
final Color? shadowColor;
|
final Color? shadowColor;
|
||||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
|
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class DividerWithPadding extends StatelessWidget {
|
class DividerWithPadding extends StatelessWidget {
|
||||||
final double left, top, right, bottom, thinckness;
|
final double left, top, right, bottom, thinckness;
|
||||||
const DividerWithPadding({
|
const DividerWithPadding({
|
||||||
Key? key,
|
super.key,
|
||||||
this.left = 0,
|
this.left = 0,
|
||||||
this.top = 0,
|
this.top = 0,
|
||||||
this.right = 0,
|
this.right = 0,
|
||||||
this.bottom = 0,
|
this.bottom = 0,
|
||||||
this.thinckness = 0.5,
|
this.thinckness = 0.5,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
|
@ -10,12 +10,12 @@ class DynamicFAB extends StatelessWidget {
|
||||||
final Function? onPressedFunction;
|
final Function? onPressedFunction;
|
||||||
|
|
||||||
const DynamicFAB({
|
const DynamicFAB({
|
||||||
Key? key,
|
super.key,
|
||||||
this.isKeypadOpen,
|
this.isKeypadOpen,
|
||||||
this.buttonText,
|
this.buttonText,
|
||||||
this.isFormValid,
|
this.isFormValid,
|
||||||
this.onPressedFunction,
|
this.onPressedFunction,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -60,6 +60,7 @@ class DynamicFAB extends StatelessWidget {
|
||||||
} else {
|
} else {
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
|
height: 56,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GradientButton extends StatelessWidget {
|
class GradientButton extends StatelessWidget {
|
||||||
|
@ -15,17 +13,21 @@ class GradientButton extends StatelessWidget {
|
||||||
// padding between the text and icon
|
// padding between the text and icon
|
||||||
final double paddingValue;
|
final double paddingValue;
|
||||||
|
|
||||||
|
// used when two icons are in row
|
||||||
|
final bool reversedGradient;
|
||||||
|
|
||||||
const GradientButton({
|
const GradientButton({
|
||||||
Key? key,
|
super.key,
|
||||||
this.linearGradientColors = const [
|
this.linearGradientColors = const [
|
||||||
Color.fromARGB(255, 133, 44, 210),
|
Color.fromARGB(255, 133, 44, 210),
|
||||||
Color.fromARGB(255, 187, 26, 93),
|
Color.fromARGB(255, 187, 26, 93),
|
||||||
],
|
],
|
||||||
|
this.reversedGradient = false,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.text = '',
|
this.text = '',
|
||||||
this.iconData,
|
this.iconData,
|
||||||
this.paddingValue = 0.0,
|
this.paddingValue = 0.0,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -71,7 +73,9 @@ class GradientButton extends StatelessWidget {
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
begin: const Alignment(0.1, -0.9),
|
begin: const Alignment(0.1, -0.9),
|
||||||
end: const Alignment(-0.6, 0.9),
|
end: const Alignment(-0.6, 0.9),
|
||||||
colors: linearGradientColors,
|
colors: reversedGradient
|
||||||
|
? linearGradientColors.reversed.toList()
|
||||||
|
: linearGradientColors,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
|
|
||||||
|
|
||||||
import 'package:ente_auth/ente_theme_data.dart';
|
import 'package:ente_auth/ente_theme_data.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class LinearProgressDialog extends StatefulWidget {
|
class LinearProgressDialog extends StatefulWidget {
|
||||||
final String message;
|
final String message;
|
||||||
|
|
||||||
const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
|
const LinearProgressDialog(this.message, {super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LinearProgressDialogState createState() => LinearProgressDialogState();
|
LinearProgressDialogState createState() => LinearProgressDialogState();
|
||||||
|
@ -29,8 +27,8 @@ class LinearProgressDialogState extends State<LinearProgressDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return PopScope(
|
||||||
onWillPop: () async => false,
|
canPop: false,
|
||||||
child: AlertDialog(
|
child: AlertDialog(
|
||||||
title: Text(
|
title: Text(
|
||||||
widget.message,
|
widget.message,
|
||||||
|
|
|
@ -11,8 +11,8 @@ class EnteLoadingWidget extends StatelessWidget {
|
||||||
this.size = 14,
|
this.size = 14,
|
||||||
this.padding = 5,
|
this.padding = 5,
|
||||||
this.alignment = Alignment.center,
|
this.alignment = Alignment.center,
|
||||||
Key? key,
|
super.key,
|
||||||
}) : super(key: key);
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|