Compare commits

..

1 Commits

Author SHA1 Message Date
Neeraj Gupta
6146350aae [docs] Update custom server section for cli 2024-03-13 12:02:36 +05:30
671 changed files with 16018 additions and 31097 deletions

View File

@@ -2,15 +2,15 @@ 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 every 24 hours]
- cron: "50 1 * * 2,5" - cron: "50 1 * * *"
# Also allow manually running the workflow # Also allow manually running the workflow
workflow_dispatch: workflow_dispatch:
@@ -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: false upload_translations: true
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

View File

@@ -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, "deploy/**"] branches-ignore: [main]
paths: paths:
- "auth/**" - "auth/**"
- ".github/workflows/auth-lint.yml" - ".github/workflows/auth-lint.yml"
env: env:
FLUTTER_VERSION: "3.19.3" FLUTTER_VERSION: "3.16.9"
jobs: jobs:
lint: lint:

View File

@@ -29,11 +29,11 @@ on:
- "auth-v*" - "auth-v*"
env: env:
FLUTTER_VERSION: "3.19.3" FLUTTER_VERSION: "3.13.4"
jobs: jobs:
build-ubuntu: build-ubuntu:
runs-on: ubuntu-20.04 runs-on: ubuntu-latest
defaults: defaults:
run: run:
@@ -72,8 +72,6 @@ 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:
@@ -85,7 +83,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 appindicator3-0.1 libappindicator3-dev 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
- name: Install appimagetool - name: Install appimagetool
run: | run: |
@@ -94,6 +92,8 @@ 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,8 +118,6 @@ 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 }}
@@ -151,6 +149,8 @@ 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,9 +159,13 @@ 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 }}"
@@ -171,7 +175,9 @@ 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
run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows # Temporarily disable desktop builds
if: false
run: tar.exe -a -c -f auth/artifacts/ente-${{ github.ref_name }}-windows.zip auth/ente-${{ github.ref_name }}-windows
- name: Create a draft GitHub release - name: Create a draft GitHub release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
@@ -242,6 +248,8 @@ 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
@@ -249,12 +257,16 @@ 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 \

View File

@@ -1,24 +0,0 @@
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 }}

View File

@@ -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, "deploy/**"] branches-ignore: [main]
paths: paths:
- "docs/**" - "docs/**"
- ".github/workflows/docs-verify-build.yml" - ".github/workflows/docs-verify-build.yml"

View File

@@ -2,15 +2,15 @@ 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 every 24 hours]
- cron: "40 1 * * 2,5" - cron: "40 1 * * *"
# Also allow manually running the workflow # Also allow manually running the workflow
workflow_dispatch: workflow_dispatch:
@@ -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: false upload_translations: true
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

View File

@@ -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, "deploy/**"] branches-ignore: [main]
paths: paths:
- "mobile/**" - "mobile/**"
- ".github/workflows/mobile-lint.yml" - ".github/workflows/mobile-lint.yml"

View File

@@ -49,7 +49,7 @@ jobs:
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }} SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
- name: Checksum - name: Checksum
run: sha256sum build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk > build/app/outputs/flutter-apk/sha256sum run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum
- name: Create a draft GitHub release - name: Create a draft GitHub release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1

View File

@@ -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, "deploy/**"] branches-ignore: [main]
paths: paths:
- "server/**" - "server/**"
- ".github/workflows/server-lint.yml" - ".github/workflows/server-lint.yml"

View File

@@ -2,21 +2,15 @@ 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] # See: [Note: Run every 24 hours]
# - cron: "20 1 * * *"
# The last (5th) component of the cron syntax denotes the day of the
# week, with 0 == SUN and 6 == SAT. So, for example, to run on every TUE
# and FRI, this can be set to `2,5`.
#
# See also: [Note: Run workflow every 24 hours]
- cron: "20 1 * * 2,5"
# Also allow manually running the workflow # Also allow manually running the workflow
workflow_dispatch: workflow_dispatch:
@@ -34,7 +28,7 @@ jobs:
base_path: "web/" base_path: "web/"
config: "web/crowdin.yml" config: "web/crowdin.yml"
upload_sources: true upload_sources: true
upload_translations: false upload_translations: true
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

View File

@@ -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, "deploy/**"] branches-ignore: [main]
paths: paths:
- "web/**" - "web/**"
- ".github/workflows/web-lint.yml" - ".github/workflows/web-lint.yml"

View File

@@ -2,7 +2,7 @@ name: "Nightly (web)"
on: on:
schedule: schedule:
# [Note: Run workflow every 24 hours] # [Note: Run every 24 hours]
# #
# Run every 24 hours - First field is minute, second is hour of the day # Run every 24 hours - First field is minute, second is hour of the day
# This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to # This runs 23:15 UTC everyday - 1 and 15 are just arbitrary offset to

7
.gitmodules vendored
View File

@@ -9,9 +9,16 @@
[submodule "auth/assets/simple-icons"] [submodule "auth/assets/simple-icons"]
path = auth/assets/simple-icons path = auth/assets/simple-icons
url = https://github.com/simple-icons/simple-icons.git url = https://github.com/simple-icons/simple-icons.git
[submodule "mobile/thirdparty/flutter"]
path = mobile/thirdparty/flutter
url = https://github.com/flutter/flutter.git
branch = stable
[submodule "mobile/plugins/clip_ggml"] [submodule "mobile/plugins/clip_ggml"]
path = mobile/plugins/clip_ggml path = mobile/plugins/clip_ggml
url = https://github.com/ente-io/clip-ggml.git url = https://github.com/ente-io/clip-ggml.git
[submodule "mobile/thirdparty/isar"]
path = mobile/thirdparty/isar
url = https://github.com/isar/isar
[submodule "web/apps/photos/thirdparty/ffmpeg-wasm"] [submodule "web/apps/photos/thirdparty/ffmpeg-wasm"]
path = web/apps/photos/thirdparty/ffmpeg-wasm path = web/apps/photos/thirdparty/ffmpeg-wasm
url = https://github.com/abhinavkgrd/ffmpeg.wasm.git url = https://github.com/abhinavkgrd/ffmpeg.wasm.git

9
auth/.gitignore vendored
View File

@@ -9,20 +9,12 @@
.history .history
.svn/ .svn/
# Editors
.vscode/
# IntelliJ related # IntelliJ related
*.iml *.iml
*.ipr *.ipr
*.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/
@@ -40,4 +32,3 @@ 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/

View File

@@ -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 and should not be manually edited. # This file should be version controlled.
version: version:
revision: "ba393198430278b6595976de84fe170f553cc728" revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
channel: "[user-branch]" channel: unknown
project_type: app project_type: app
@@ -13,26 +13,17 @@ project_type: app
migration: migration:
platforms: platforms:
- platform: root - platform: root
create_revision: ba393198430278b6595976de84fe170f553cc728 create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ba393198430278b6595976de84fe170f553cc728 base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- platform: android
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: ios
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: linux - platform: linux
create_revision: ba393198430278b6595976de84fe170f553cc728 create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ba393198430278b6595976de84fe170f553cc728 base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- platform: macos - platform: macos
create_revision: ba393198430278b6595976de84fe170f553cc728 create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ba393198430278b6595976de84fe170f553cc728 base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
- platform: web
create_revision: ba393198430278b6595976de84fe170f553cc728
base_revision: ba393198430278b6595976de84fe170f553cc728
- platform: windows - platform: windows
create_revision: ba393198430278b6595976de84fe170f553cc728 create_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
base_revision: ba393198430278b6595976de84fe170f553cc728 base_revision: ee4e09cce01d6f2d7f4baebd247fde02e5008851
# User provided section # User provided section

View File

@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 34 compileSdkVersion 33
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 21 minSdkVersion 20
targetSdkVersion 33 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
@@ -56,11 +56,11 @@ android {
signingConfigs { signingConfigs {
release { release {
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS") keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD") keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD") storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
} }
} }
flavorDimensions "default" flavorDimensions "default"
@@ -109,7 +109,6 @@ 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'

View File

@@ -62,11 +62,6 @@
{ {
"title": "Crowdpear" "title": "Crowdpear"
}, },
{
"title": "DCS",
"altNames": ["Digital Combat Simulator"],
"slug": "dcs"
},
{ {
"title": "DEGIRO" "title": "DEGIRO"
}, },
@@ -272,10 +267,6 @@
"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",
@@ -365,10 +356,6 @@
{ {
"title": "Wise" "title": "Wise"
}, },
{
"title": "WYZE",
"slug": "wyze"
},
{ {
"title": "X", "title": "X",
"altNames": ["twitter"], "altNames": ["twitter"],

View File

@@ -1,9 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,25 +0,0 @@
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

View File

@@ -1,14 +1,7 @@
# Releases # Releases
Create a PR to bump up the version in `pubspec.yaml`. Create a PR to bump up the version in `pubspec.yaml`. Once that is merged, tag
main, and push the tag.
> [!NOTE]
>
> Use [semver](https://semver.org/) for the tags, with `auth-` as a prefix.
> Multiple beta releases for the same upcoming version can be done by adding
> build metadata at the end, e.g. `auth-v1.2.3-beta+3`.
Once that is merged, tag main, and push the tag.
```sh ```sh
git tag auth-v1.2.3 git tag auth-v1.2.3
@@ -23,11 +16,6 @@ This'll trigger a GitHub workflow that:
* Creates a new release in the internal track on Play Store. * Creates a new release in the internal track on Play Store.
Once the workflow completes, go to the draft GitHub release that was created. Once the workflow completes, go to the draft GitHub release that was created.
> [!NOTE]
>
> Keep the title of the release same as the tag.
Set "Previous tag" to the last release of auth and press "Generate release Set "Previous tag" to the last release of auth and press "Generate release
notes". The generated release note will contain all PRs and new contributors notes". The generated release note will contain all PRs and new contributors
from all the releases in the monorepo, so you'll need to filter them to keep from all the releases in the monorepo, so you'll need to filter them to keep

View File

@@ -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/ente If you have feature requests, please create an issue @ https://github.com/ente-io/auth

View File

@@ -1,9 +1,7 @@
PODS: PODS:
- app_links (0.0.1): - connectivity (0.0.1):
- Flutter - Flutter
- connectivity_plus (0.0.1): - Reachability
- 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):
@@ -47,26 +45,27 @@ 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_ios (0.0.1): - flutter_inappwebview (0.0.1):
- Flutter - Flutter
- flutter_inappwebview_ios/Core (= 0.0.1) - flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0) - OrderedSet (~> 5.0)
- flutter_inappwebview_ios/Core (0.0.1): - flutter_inappwebview/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
- local_auth_darwin (0.0.1): - FMDB (2.7.5):
- Flutter - FMDB/standard (= 2.7.5)
- 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):
@@ -83,65 +82,46 @@ PODS:
- qr_code_scanner (0.2.0): - qr_code_scanner (0.2.0):
- Flutter - Flutter
- MTBBarcodeScanner - MTBBarcodeScanner
- ReachabilitySwift (5.2.1) - Reachability (3.2)
- SDWebImage (5.19.0): - SDWebImage (5.17.0):
- SDWebImage/Core (= 5.19.0) - SDWebImage/Core (= 5.17.0)
- SDWebImage/Core (5.19.0) - SDWebImage/Core (5.17.0)
- Sentry/HybridSDK (8.21.0): - Sentry/HybridSDK (8.9.1):
- SentryPrivate (= 8.21.0) - SentryPrivate (= 8.9.1)
- sentry_flutter (0.0.1): - sentry_flutter (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- Sentry/HybridSDK (= 8.21.0) - Sentry/HybridSDK (= 8.9.1)
- SentryPrivate (8.21.0) - SentryPrivate (8.9.1)
- 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
- FlutterMacOS - FMDB (>= 2.7.5)
- sqlite3 (3.45.1):
- sqlite3/common (= 3.45.1)
- sqlite3/common (3.45.1)
- sqlite3/fts5 (3.45.1):
- sqlite3/common
- sqlite3/perf-threadsafe (3.45.1):
- sqlite3/common
- sqlite3/rtree (3.45.1):
- sqlite3/common
- sqlite3_flutter_libs (0.0.1):
- Flutter
- sqlite3 (~> 3.45.1)
- sqlite3/fts5
- sqlite3/perf-threadsafe
- sqlite3/rtree
- SwiftyGif (5.4.4) - SwiftyGif (5.4.4)
- Toast (4.1.0) - Toast (4.0.0)
- uni_links (0.0.1):
- Flutter
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`) - connectivity (from `.symlinks/plugins/connectivity/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_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/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`)
@@ -151,31 +131,27 @@ 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`)
- smart_auth (from `.symlinks/plugins/smart_auth/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) - uni_links (from `.symlinks/plugins/uni_links/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
- ReachabilitySwift - Reachability
- SDWebImage - SDWebImage
- Sentry - Sentry
- SentryPrivate - SentryPrivate
- sqlite3
- SwiftyGif - SwiftyGif
- Toast - Toast
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links: connectivity:
:path: ".symlinks/plugins/app_links/ios" :path: ".symlinks/plugins/connectivity/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:
@@ -188,20 +164,18 @@ 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_ios: flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios" :path: ".symlinks/plugins/flutter_inappwebview/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:
@@ -220,58 +194,50 @@ 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/darwin" :path: ".symlinks/plugins/sqflite/ios"
sqlite3_flutter_libs: uni_links:
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios" :path: ".symlinks/plugins/uni_links/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:
app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795 connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de file_picker: ce3938a0df3cc1ef404671531facef740d03f920
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
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
local_auth_darwin: c7e464000a6a89e952235699e32b329457608d98 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9 local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
ReachabilitySwift: 5ae15e16814b5f9ef568963fb2c87aeb49158c66 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
SDWebImage: 981fd7e860af070920f249fd092420006014c3eb SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
Sentry: ebc12276bd17613a114ab359074096b6b3725203 Sentry: e3203780941722a1fcfee99e351de14244c7f806
sentry_flutter: dff1df05dc39c83d04f9330b36360fc374574c5e sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
SentryPrivate: d651efb234cf385ec9a1cdd3eff94b5e78a0e0fe SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Toast: ec33c32b8688982cecc6348adeae667c1b9938da Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586 uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb

View File

@@ -159,7 +159,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1510; LastUpgradeCheck = 1430;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1510" LastUpgradeVersion = "1430"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@@ -1,6 +1,5 @@
import Flutter
import UIKit import UIKit
import app_links import Flutter
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
@@ -9,15 +8,6 @@ import app_links
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)
} }
} }

View File

@@ -78,9 +78,5 @@
</array> </array>
<key>UIViewControllerBasedStatusBarAppearance</key> <key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@@ -12,18 +12,15 @@ 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:system_tray/system_tray.dart';
import 'package:window_manager/window_manager.dart';
class App extends StatefulWidget { class App extends StatefulWidget {
final Locale locale; final Locale locale;
const App({super.key, this.locale = const Locale("en")}); const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
static void setLocale(BuildContext context, Locale newLocale) { static void setLocale(BuildContext context, Locale newLocale) {
_AppState state = context.findAncestorStateOfType<_AppState>()!; _AppState state = context.findAncestorStateOfType<_AppState>()!;
@@ -34,7 +31,7 @@ class App extends StatefulWidget {
State<App> createState() => _AppState(); State<App> createState() => _AppState();
} }
class _AppState extends State<App> with WindowListener { class _AppState extends State<App> {
late StreamSubscription<SignedOutEvent> _signedOutEvent; late StreamSubscription<SignedOutEvent> _signedOutEvent;
late StreamSubscription<SignedInEvent> _signedInEvent; late StreamSubscription<SignedInEvent> _signedInEvent;
Locale? locale; Locale? locale;
@@ -44,14 +41,8 @@ class _AppState extends State<App> with WindowListener {
}); });
} }
Future<void> initWindowManager() async {
windowManager.addListener(this);
await windowManager.setPreventClose(true);
}
@override @override
void initState() { void initState() {
initWindowManager();
_signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) { _signedOutEvent = Bus.instance.on<SignedOutEvent>().listen((event) {
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
@@ -85,7 +76,6 @@ class _AppState extends State<App> with WindowListener {
@override @override
void dispose() { void dispose() {
super.dispose(); super.dispose();
windowManager.removeListener(this);
_signedOutEvent.cancel(); _signedOutEvent.cancel();
_signedInEvent.cancel(); _signedInEvent.cancel();
} }
@@ -144,15 +134,4 @@ class _AppState extends State<App> with WindowListener {
: const OnboardingPage(), : const OnboardingPage(),
}; };
} }
@override
void onWindowClose() async {
final AppWindow appWindow = AppWindow();
await appWindow.hide();
}
@override
void onWindowResize() {
WindowListenerService.instance.onWindowResize().ignore();
}
} }

View File

@@ -0,0 +1,4 @@
class AppRoute {
static const String enterSecretKeyPage = "enterSecretKeyPage";
static const String settings = "settings";
}

View File

@@ -0,0 +1,163 @@
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,
),
);
}

View File

@@ -1,5 +1,3 @@
// ignore_for_file: deprecated_member_use
import "dart:async"; import "dart:async";
import "dart:developer"; import "dart:developer";

View File

@@ -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_crypto_dart/ente_crypto_dart.dart'; import 'package:ente_auth/utils/crypto_util.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,10 +72,9 @@ 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;
@@ -163,7 +162,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), utf8.encode(password) as Uint8List,
kekSalt, kekSalt,
); );
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -173,28 +172,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 = CryptoUtil.generateKeyPair(); final keyPair = await CryptoUtil.generateKeyPair();
final encryptedSecretKeyData = final encryptedSecretKeyData =
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey); CryptoUtil.encryptSync(keyPair.sk, masterKey);
final attributes = KeyAttributes( final attributes = KeyAttributes(
CryptoUtil.bin2base64(kekSalt), Sodium.bin2base64(kekSalt),
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), Sodium.bin2base64(encryptedKeyData.encryptedData!),
CryptoUtil.bin2base64(encryptedKeyData.nonce!), Sodium.bin2base64(encryptedKeyData.nonce!),
CryptoUtil.bin2base64(keyPair.publicKey), Sodium.bin2base64(keyPair.pk),
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!), Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!), Sodium.bin2base64(encryptedSecretKeyData.nonce!),
derivedKeyResult.memLimit, derivedKeyResult.memLimit,
derivedKeyResult.opsLimit, derivedKeyResult.opsLimit,
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!), Sodium.bin2base64(encryptedMasterKey.encryptedData!),
CryptoUtil.bin2base64(encryptedMasterKey.nonce!), Sodium.bin2base64(encryptedMasterKey.nonce!),
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!), Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!), Sodium.bin2base64(encryptedRecoveryKey.nonce!),
); );
final privateAttributes = PrivateKeyAttributes( final privateAttributes = PrivateKeyAttributes(
CryptoUtil.bin2base64(masterKey), Sodium.bin2base64(masterKey),
CryptoUtil.bin2hex(recoveryKey), Sodium.bin2hex(recoveryKey),
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()), Sodium.bin2base64(keyPair.sk),
); );
return KeyGenResult(attributes, privateAttributes, loginKey); return KeyGenResult(attributes, privateAttributes, loginKey);
} }
@@ -209,7 +208,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), utf8.encode(password) as Uint8List,
kekSalt, kekSalt,
); );
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -221,9 +220,9 @@ class Configuration {
final existingAttributes = getKeyAttributes(); final existingAttributes = getKeyAttributes();
final updatedAttributes = existingAttributes!.copyWith( final updatedAttributes = existingAttributes!.copyWith(
kekSalt: CryptoUtil.bin2base64(kekSalt), kekSalt: Sodium.bin2base64(kekSalt),
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
memLimit: derivedKeyResult.memLimit, memLimit: derivedKeyResult.memLimit,
opsLimit: derivedKeyResult.opsLimit, opsLimit: derivedKeyResult.opsLimit,
); );
@@ -241,8 +240,8 @@ class Configuration {
}) async { }) async {
_logger.info('Start decryptAndSaveSecrets'); _logger.info('Start decryptAndSaveSecrets');
keyEncryptionKey ??= await CryptoUtil.deriveKey( keyEncryptionKey ??= await CryptoUtil.deriveKey(
utf8.encode(password), utf8.encode(password) as Uint8List,
CryptoUtil.base642bin(attributes.kekSalt), Sodium.base642bin(attributes.kekSalt),
attributes.memLimit, attributes.memLimit,
attributes.opsLimit, attributes.opsLimit,
); );
@@ -251,31 +250,31 @@ class Configuration {
Uint8List key; Uint8List key;
try { try {
key = CryptoUtil.decryptSync( key = CryptoUtil.decryptSync(
CryptoUtil.base642bin(attributes.encryptedKey), Sodium.base642bin(attributes.encryptedKey),
keyEncryptionKey, keyEncryptionKey,
CryptoUtil.base642bin(attributes.keyDecryptionNonce), Sodium.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(CryptoUtil.bin2base64(key)); await setKey(Sodium.bin2base64(key));
final secretKey = CryptoUtil.decryptSync( final secretKey = CryptoUtil.decryptSync(
CryptoUtil.base642bin(attributes.encryptedSecretKey), Sodium.base642bin(attributes.encryptedSecretKey),
key, key,
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), Sodium.base642bin(attributes.secretKeyDecryptionNonce),
); );
_logger.info("secret-key done"); _logger.info("secret-key done");
await setSecretKey(CryptoUtil.bin2base64(secretKey)); await setSecretKey(Sodium.bin2base64(secretKey));
final token = CryptoUtil.openSealSync( final token = CryptoUtil.openSealSync(
CryptoUtil.base642bin(getEncryptedToken()!), Sodium.base642bin(getEncryptedToken()!),
CryptoUtil.base642bin(attributes.publicKey), Sodium.base642bin(attributes.publicKey),
secretKey, secretKey,
); );
_logger.info('appToken done'); _logger.info('appToken done');
await setToken( await setToken(
CryptoUtil.bin2base64(token, urlSafe: true), Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
); );
return keyEncryptionKey; return keyEncryptionKey;
} }
@@ -294,28 +293,28 @@ class Configuration {
Uint8List masterKey; Uint8List masterKey;
try { try {
masterKey = await CryptoUtil.decrypt( masterKey = await CryptoUtil.decrypt(
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
CryptoUtil.hex2bin(recoveryKey), Sodium.hex2bin(recoveryKey),
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce), Sodium.base642bin(attributes.masterKeyDecryptionNonce),
); );
} catch (e) { } catch (e) {
_logger.severe(e); _logger.severe(e);
rethrow; rethrow;
} }
await setKey(CryptoUtil.bin2base64(masterKey)); await setKey(Sodium.bin2base64(masterKey));
final secretKey = CryptoUtil.decryptSync( final secretKey = CryptoUtil.decryptSync(
CryptoUtil.base642bin(attributes.encryptedSecretKey), Sodium.base642bin(attributes.encryptedSecretKey),
masterKey, masterKey,
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), Sodium.base642bin(attributes.secretKeyDecryptionNonce),
); );
await setSecretKey(CryptoUtil.bin2base64(secretKey)); await setSecretKey(Sodium.bin2base64(secretKey));
final token = CryptoUtil.openSealSync( final token = CryptoUtil.openSealSync(
CryptoUtil.base642bin(getEncryptedToken()!), Sodium.base642bin(getEncryptedToken()!),
CryptoUtil.base642bin(attributes.publicKey), Sodium.base642bin(attributes.publicKey),
secretKey, secretKey,
); );
await setToken( await setToken(
CryptoUtil.bin2base64(token, urlSafe: true), Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
); );
} }
@@ -408,31 +407,27 @@ class Configuration {
} }
Uint8List? getKey() { Uint8List? getKey() {
return _key == null ? null : CryptoUtil.base642bin(_key!); return _key == null ? null : Sodium.base642bin(_key!);
} }
Uint8List? getSecretKey() { Uint8List? getSecretKey() {
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!); return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
} }
Uint8List? getAuthSecretKey() { Uint8List? getAuthSecretKey() {
return _authSecretKey == null return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
? null
: CryptoUtil.base642bin(_authSecretKey!);
} }
Uint8List? getOfflineSecretKey() { Uint8List? getOfflineSecretKey() {
return _offlineAuthKey == null return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
? null
: CryptoUtil.base642bin(_offlineAuthKey!);
} }
Uint8List getRecoveryKey() { Uint8List getRecoveryKey() {
final keyAttributes = getKeyAttributes()!; final keyAttributes = getKeyAttributes()!;
return CryptoUtil.decryptSync( return CryptoUtil.decryptSync(
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
getKey()!, getKey()!,
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce), Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
); );
} }
@@ -459,7 +454,7 @@ class Configuration {
iOptions: _secureStorageOptionsIOS, iOptions: _secureStorageOptionsIOS,
); );
} else { } else {
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey()); _offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
await _secureStorage.write( await _secureStorage.write(
key: offlineAuthSecretKey, key: offlineAuthSecretKey,
value: _offlineAuthKey, value: _offlineAuthKey,

View File

@@ -7,8 +7,7 @@ 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 githubIssuesUrl = const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions";
"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

View File

@@ -1,9 +1,9 @@
class InvalidFileError extends ArgumentError { class InvalidFileError extends ArgumentError {
InvalidFileError(String super.message); InvalidFileError(String message) : super(message);
} }
class InvalidFileUploadState extends AssertionError { class InvalidFileUploadState extends AssertionError {
InvalidFileUploadState(String super.message); InvalidFileUploadState(String message) : super(message);
} }
class SubscriptionAlreadyClaimedError extends Error {} class SubscriptionAlreadyClaimedError extends Error {}
@@ -30,15 +30,19 @@ class UnauthorizedError extends Error {}
class RequestCancelledError extends Error {} class RequestCancelledError extends Error {}
class InvalidSyncStatusError extends AssertionError { class InvalidSyncStatusError extends AssertionError {
InvalidSyncStatusError(String super.message); InvalidSyncStatusError(String message) : super(message);
} }
class UnauthorizedEditError extends AssertionError {} class UnauthorizedEditError extends AssertionError {}
class InvalidStateError extends AssertionError { class InvalidStateError extends AssertionError {
InvalidStateError(String super.message); InvalidStateError(String message) : 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 {}

View File

@@ -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(debugPrint); text.chunked(logChunkSize).forEach(print);
} }
/// 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 Exception catch (_) {} } on FormatException {}
} }
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();
} on Exception catch (_) {} } catch (ignore) {}
} }
} }

View File

@@ -46,7 +46,7 @@ class TunneledTransport implements Transport {
_options.logger( _options.logger(
SentryLevel.error, SentryLevel.error,
'API returned an error, statusCode = ${response.statusCode}, ' 'API returned an error, statusCode = ${response.statusCode}, '
'body = ${response.body}', 'body = ${response.body}',
); );
} }
return const SentryId.empty(); return const SentryId.empty();
@@ -65,8 +65,8 @@ class TunneledTransport implements Transport {
} }
Future<StreamedRequest> _createStreamedRequest( Future<StreamedRequest> _createStreamedRequest(
SentryEnvelope envelope, SentryEnvelope envelope,
) async { ) async {
final streamedRequest = StreamedRequest('POST', _tunnel); final streamedRequest = StreamedRequest('POST', _tunnel);
envelope envelope
.envelopeStream(_options) .envelopeStream(_options)
@@ -91,10 +91,10 @@ class _CredentialBuilder {
_clock = clock; _clock = clock;
factory _CredentialBuilder( factory _CredentialBuilder(
Dsn? dsn, Dsn? dsn,
String sdkIdentifier, String sdkIdentifier,
ClockProvider clock, ClockProvider clock,
) { ) {
final authHeader = _buildAuthHeader( final authHeader = _buildAuthHeader(
publicKey: dsn?.publicKey, publicKey: dsn?.publicKey,
secretKey: dsn?.secretKey, secretKey: dsn?.secretKey,

View File

@@ -4,10 +4,9 @@ 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;
@@ -17,41 +16,34 @@ class Network {
late Dio _enteDio; late Dio _enteDio;
Future<void> init() async { Future<void> init() async {
if (PlatformUtil.isMobile()) await FkUserAgent.init(); await FkUserAgent.init();
final packageInfo = await PackageInfoUtil().getPackageInfo(); final packageInfo = await PackageInfo.fromPlatform();
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: Duration(milliseconds: kConnectTimeout), connectTimeout: kConnectTimeout,
headers: { headers: {
HttpHeaders.userAgentHeader: PlatformUtil.isMobile() HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
? FkUserAgent.userAgent 'X-Client-Version': packageInfo.version,
: Platform.operatingSystem, 'X-Client-Package': packageInfo.packageName,
'X-Client-Version': version,
'X-Client-Package': packageName,
}, },
), ),
); );
_enteDio = Dio( _enteDio = Dio(
BaseOptions( BaseOptions(
baseUrl: endpoint, baseUrl: endpoint,
connectTimeout: Duration(milliseconds: kConnectTimeout), connectTimeout: kConnectTimeout,
headers: { headers: {
if (PlatformUtil.isMobile()) HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
HttpHeaders.userAgentHeader: FkUserAgent.userAgent 'X-Client-Version': packageInfo.version,
else 'X-Client-Package': packageInfo.packageName,
HttpHeaders.userAgentHeader: Platform.operatingSystem,
'X-Client-Version': version,
'X-Client-Package': packageName,
}, },
), ),
); );
_setupInterceptors(endpoint); _setupInterceptors(endpoint);
Bus.instance.on<EndpointUpdatedEvent>().listen((event) { Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
final endpoint = Configuration.instance.getHttpEndpoint(); final endpoint = Configuration.instance.getHttpEndpoint();
_enteDio.options.baseUrl = endpoint; _enteDio.options.baseUrl = endpoint;

View File

@@ -5,16 +5,12 @@ 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),
@@ -76,42 +72,24 @@ 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(
radioTheme: RadioThemeData( fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
fillColor: 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;
return null; }),
} ), switchTheme: SwitchThemeData(
if (states.contains(MaterialState.selected)) { thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
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;
}), }),
), trackColor: 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) { return null;
if (states.contains(MaterialState.disabled)) { }),
return null; ), colorScheme: const ColorScheme.light(
}
if (states.contains(MaterialState.selected)) {
return const Color.fromRGBO(102, 187, 106, 1);
}
return null;
}),
trackColor:
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;
}),
),
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)),
@@ -120,9 +98,6 @@ 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:
@@ -130,7 +105,6 @@ 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(
@@ -190,43 +164,24 @@ final darkThemeData = ThemeData(
return const Color.fromRGBO(158, 158, 158, 1); return const Color.fromRGBO(158, 158, 158, 1);
} }
}), }),
), ), radioTheme: RadioThemeData(
radioTheme: RadioThemeData( fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
fillColor: 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;
return null; }),
} ), switchTheme: SwitchThemeData(
if (states.contains(MaterialState.selected)) { thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
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;
}), }),
), trackColor: 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) { return null;
if (states.contains(MaterialState.disabled)) { }),
return null; ), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
}
if (states.contains(MaterialState.selected)) {
return const Color.fromRGBO(102, 187, 106, 1);
}
return null;
}),
trackColor:
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;
}),
),
colorScheme: const ColorScheme.dark(primary: Colors.white)
.copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
); );
TextTheme _buildTextTheme(Color textColor) { TextTheme _buildTextTheme(Color textColor) {
@@ -445,7 +400,6 @@ 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(
@@ -482,9 +436,7 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
}) { }) {
return ElevatedButtonThemeData( return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
foregroundColor: onPrimary, foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
backgroundColor: primary,
elevation: elevation,
alignment: Alignment.center, alignment: Alignment.center,
textStyle: const TextStyle( textStyle: const TextStyle(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View File

@@ -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 DioException catch (e) { } on DioError 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 DioException && e.response?.statusCode == 401) { if (e is DioError && e.response?.statusCode == 401) {
throw UnauthorizedError(); throw UnauthorizedError();
} else { } else {
rethrow; rethrow;

View File

@@ -1 +0,0 @@
{}

View File

@@ -144,8 +144,6 @@
"enterCodeHint": "Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.", "enterCodeHint": "Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.",
"lostDeviceTitle": "Gerät verloren?", "lostDeviceTitle": "Gerät verloren?",
"twoFactorAuthTitle": "Zwei-Faktor-Authentifizierung", "twoFactorAuthTitle": "Zwei-Faktor-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",
@@ -406,15 +404,5 @@
"signOutOtherDevices": "Andere Geräte abmelden", "signOutOtherDevices": "Andere Geräte abmelden",
"doNotSignOut": "Nicht abmelden", "doNotSignOut": "Nicht abmelden",
"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...",
"waitingForVerification": "Warte auf Bestätigung...",
"passkey": "Passkey",
"developerSettingsWarning": "Sind Sie sicher, dass Sie die Entwicklereinstellungen ändern möchten?",
"developerSettings": "Entwicklereinstellungen",
"serverEndpoint": "Server Endpunkt",
"invalidEndpoint": "Ungültiger Endpunkt",
"invalidEndpointMessage": "Der eingegebene Endpunkt ist ungültig. Bitte geben Sie einen gültigen Endpunkt ein und versuchen Sie es erneut.",
"endpointUpdatedMessage": "Endpunkt erfolgreich aktualisiert",
"customEndpoint": "Mit {endpoint} verbunden"
} }

View File

@@ -199,10 +199,6 @@
"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}",
@@ -411,7 +407,6 @@
"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",

View File

@@ -144,8 +144,6 @@
"enterCodeHint": "認証アプリに表示された 6 桁のコードを入力してください", "enterCodeHint": "認証アプリに表示された 6 桁のコードを入力してください",
"lostDeviceTitle": "デバイスを紛失しましたか?", "lostDeviceTitle": "デバイスを紛失しましたか?",
"twoFactorAuthTitle": "2 要素認証", "twoFactorAuthTitle": "2 要素認証",
"passkeyAuthTitle": "パスキー認証",
"verifyPasskey": "パスキーの認証",
"recoverAccount": "アカウントを回復", "recoverAccount": "アカウントを回復",
"enterRecoveryKeyHint": "回復キーを入力", "enterRecoveryKeyHint": "回復キーを入力",
"recover": "回復", "recover": "回復",
@@ -406,15 +404,5 @@
"signOutOtherDevices": "他のデバイスからサインアウトする", "signOutOtherDevices": "他のデバイスからサインアウトする",
"doNotSignOut": "サインアウトしない", "doNotSignOut": "サインアウトしない",
"hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)", "hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)",
"hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!", "hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!"
"waitingForBrowserRequest": "ブラウザのリクエストを待っています...",
"waitingForVerification": "認証を待っています...",
"passkey": "パスキー",
"developerSettingsWarning": "開発者向け設定を変更してもよろしいですか?",
"developerSettings": "開発者向け設定",
"serverEndpoint": "サーバーエンドポイント",
"invalidEndpoint": "無効なエンドポイントです",
"invalidEndpointMessage": "入力されたエンドポイントは無効です。有効なエンドポイントを入力して再試行してください。",
"endpointUpdatedMessage": "エンドポイントの更新に成功しました",
"customEndpoint": "{endpoint} に接続しました"
} }

View File

@@ -144,8 +144,6 @@
"enterCodeHint": "Digite o código de 6 dígitos de\nseu aplicativo autenticador", "enterCodeHint": "Digite o código de 6 dígitos de\nseu aplicativo autenticador",
"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",
"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",
@@ -406,15 +404,5 @@
"signOutOtherDevices": "Terminar sessão em outros dispositivos", "signOutOtherDevices": "Terminar sessão em outros dispositivos",
"doNotSignOut": "Não encerrar sessão", "doNotSignOut": "Não encerrar sessão",
"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...",
"waitingForVerification": "Esperando por verificação...",
"passkey": "Chave de acesso",
"developerSettingsWarning": "Tem certeza de que deseja modificar as configurações de Desenvolvedor?",
"developerSettings": "Configurações de desenvolvedor",
"serverEndpoint": "Endpoint do servidor",
"invalidEndpoint": "Endpoint inválido",
"invalidEndpointMessage": "Desculpe, o endpoint que você inseriu é inválido. Por favor, insira um endpoint válido e tente novamente.",
"endpointUpdatedMessage": "Endpoint atualizado com sucesso",
"customEndpoint": "Conectado a {endpoint}"
} }

View File

@@ -131,16 +131,6 @@
"about": "Om", "about": "Om",
"terms": "Villkor", "terms": "Villkor",
"warning": "Varning", "warning": "Varning",
"importSuccessDesc": "Du har importerat {count} koder!",
"@importSuccessDesc": {
"placeholders": {
"count": {
"description": "The number of codes imported",
"type": "int",
"example": "1"
}
}
},
"pendingSyncs": "Varning", "pendingSyncs": "Varning",
"activeSessions": "Aktiva sessioner", "activeSessions": "Aktiva sessioner",
"enterPassword": "Ange lösenord", "enterPassword": "Ange lösenord",
@@ -153,7 +143,5 @@
"iOSOkButton": "OK", "iOSOkButton": "OK",
"@iOSOkButton": { "@iOSOkButton": {
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
}, }
"noInternetConnection": "Ingen internetanslutning",
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen."
} }

View File

@@ -145,7 +145,6 @@
"lostDeviceTitle": "丢失了设备吗?", "lostDeviceTitle": "丢失了设备吗?",
"twoFactorAuthTitle": "双因素认证", "twoFactorAuthTitle": "双因素认证",
"passkeyAuthTitle": "通行密钥认证", "passkeyAuthTitle": "通行密钥认证",
"verifyPasskey": "验证通行密钥",
"recoverAccount": "恢复账户", "recoverAccount": "恢复账户",
"enterRecoveryKeyHint": "输入您的恢复密钥", "enterRecoveryKeyHint": "输入您的恢复密钥",
"recover": "恢复", "recover": "恢复",
@@ -408,13 +407,6 @@
"hearUsWhereTitle": "您是如何知道Ente的 (可选的)", "hearUsWhereTitle": "您是如何知道Ente的 (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!", "hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
"waitingForBrowserRequest": "正在等待浏览器请求...", "waitingForBrowserRequest": "正在等待浏览器请求...",
"waitingForVerification": "等待验证...", "launchPasskeyUrlAgain": "再次启动 通行密钥 URL",
"passkey": "通行密钥", "passkey": "通行密钥"
"developerSettingsWarning": "您确定要修改开发者设置吗?",
"developerSettings": "开发者设置",
"serverEndpoint": "服务器端点",
"invalidEndpoint": "端点无效",
"invalidEndpointMessage": "抱歉,您输入的端点无效。请输入有效的端点,然后重试。",
"endpointUpdatedMessage": "端点更新成功",
"customEndpoint": "已连接至 {endpoint}"
} }

View File

@@ -2,6 +2,7 @@ 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';
@@ -16,14 +17,11 @@ 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/platform_util.dart'; import 'package:ente_auth/utils/crypto_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';
@@ -31,60 +29,11 @@ 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:system_tray/system_tray.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/icon-light.ico' : 'assets/icon-light.png';
final AppWindow appWindow = AppWindow();
final SystemTray systemTray = SystemTray();
// We first init the systray menu
await systemTray.initSystemTray(
title: "",
iconPath: path,
);
// create context menu
final show = MenuItem(label: 'Show', onClicked: () => appWindow.show());
final hide = MenuItem(label: 'Hide', onClicked: () => appWindow.hide());
final exit = MenuItem(label: 'Exit', onClicked: () => appWindow.close());
// set context menu
await systemTray.setContextMenu([show, hide, exit]);
const kSystemTrayEventClick = 'leftMouseDown';
const kSystemTrayEventRightClick = 'rightMouseDown';
// // handle system tray event
systemTray.registerSystemTrayEventHandler((eventName) {
if (eventName == kSystemTrayEventClick) {
Platform.isWindows ? appWindow.show() : systemTray.popUpContextMenu();
} else if (eventName == kSystemTrayEventRightClick) {
Platform.isWindows ? systemTray.popUpContextMenu() : appWindow.show();
}
});
}
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) {
@@ -121,14 +70,10 @@ 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: dir, logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
maxLogFiles: 5, maxLogFiles: 5,
sentryDsn: sentryDSN, sentryDsn: sentryDSN,
enableInDebugMode: true, enableInDebugMode: true,
@@ -137,19 +82,10 @@ 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 {
_registerWindowsProtocol(); // Start workers asynchronously. No need to wait for them to start
await initCryptoUtil(); Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode).ignore();
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();
@@ -164,7 +100,6 @@ 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;

View File

@@ -57,7 +57,14 @@ class Code {
updatedAlgo, updatedAlgo,
updatedType, updatedType,
updatedCounter, updatedCounter,
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}", "otpauth://${updatedType.name}/" +
updateIssuer +
":" +
updateAccount +
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
updateIssuer +
"&period=$updatePeriod&secret=" +
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
generatedID: generatedID, generatedID: generatedID,
); );
} }
@@ -76,28 +83,35 @@ class Code {
Algorithm.sha1, Algorithm.sha1,
Type.totp, Type.totp,
0, 0,
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret", "otpauth://totp/" +
issuer +
":" +
account +
"?algorithm=SHA1&digits=6&issuer=" +
issuer +
"&period=30&secret=" +
secret,
); );
} }
static Code fromRawData(String rawData) { static Code fromRawData(String rawData) {
Uri uri = Uri.parse(rawData); Uri uri = Uri.parse(rawData);
try { try {
return Code( return Code(
_getAccount(uri), _getAccount(uri),
_getIssuer(uri), _getIssuer(uri),
_getDigits(uri), _getDigits(uri),
_getPeriod(uri), _getPeriod(uri),
getSanitizedSecret(uri.queryParameters['secret']!), getSanitizedSecret(uri.queryParameters['secret']!),
_getAlgorithm(uri), _getAlgorithm(uri),
_getType(uri), _getType(uri),
_getCounter(uri), _getCounter(uri),
rawData, rawData,
); );
} catch (e) { } catch(e) {
// if account name contains # without encoding, // if account name contains # without encoding,
// rest of the url are treated as url fragment // rest of the url are treated as url fragment
if (rawData.contains("#")) { if(rawData.contains("#")) {
return Code.fromRawData(rawData.replaceAll("#", '%23')); return Code.fromRawData(rawData.replaceAll("#", '%23'));
} else { } else {
rethrow; rethrow;
@@ -127,7 +141,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/ente/pull/77 // See https://github.com/ente-io/auth/pull/77
if (issuerName.contains("period=")) { if (issuerName.contains("period=")) {
return issuerName.substring(0, issuerName.indexOf("period=")); return issuerName.substring(0, issuerName.indexOf("period="));
} }

View File

@@ -0,0 +1,9 @@
import 'dart:typed_data';
class DerivedKeyResult {
final Uint8List key;
final int memLimit;
final int opsLimit;
DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
}

View File

@@ -0,0 +1,15 @@
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,
});
}

View File

@@ -1,5 +1,4 @@
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';
@@ -29,7 +28,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({super.key}); const OnboardingPage({Key? key}) : super(key: key);
@override @override
State<OnboardingPage> createState() => _OnboardingPageState(); State<OnboardingPage> createState() => _OnboardingPageState();
@@ -87,128 +86,118 @@ class _OnboardingPageState extends State<OnboardingPage> {
} }
} }
}, },
child: SingleChildScrollView( child: Center(
child: Center( child: SingleChildScrollView(
child: ConstrainedBox( child: Padding(
constraints: padding:
const BoxConstraints.tightFor(height: 800, width: 450), const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Padding( child: Column(
padding: const EdgeInsets.symmetric( children: [
vertical: 40.0, Column(
horizontal: 40, children: [
), kDebugMode
child: Column( ? GestureDetector(
children: [ child: const Align(
Column( alignment: Alignment.topRight,
children: [ child: Text("Lang"),
kDebugMode
? GestureDetector(
child: const Align(
alignment: Alignment.topRight,
child: Text("Lang"),
),
onTap: () async {
final locale = await getLocale();
// ignore: unawaited_futures
routeToPage(
context,
LanguageSelectorPage(
appSupportedLocales,
(locale) async {
await setLocale(locale);
App.setLocale(context, locale);
},
locale,
),
).then((value) {
setState(() {});
});
},
)
: const SizedBox(),
Image.asset(
"assets/sheild-front-gradient.png",
width: 200,
height: 200,
),
const SizedBox(height: 12),
const Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 42,
),
),
const SizedBox(height: 4),
Text(
"Authenticator",
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
Text(
l10n.onBoardingBody,
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.titleLarge!
.copyWith(
color: Colors.white38,
), ),
), onTap: () async {
], final locale = await getLocale();
), // ignore: unawaited_futures
const SizedBox(height: 100), routeToPage(
Container( context,
width: double.infinity, LanguageSelectorPage(
padding: const EdgeInsets.symmetric(horizontal: 20), appSupportedLocales,
child: GradientButton( (locale) async {
onTap: _navigateToSignUpPage, await setLocale(locale);
text: l10n.newUser, App.setLocale(context, locale);
},
locale,
),
).then((value) {
setState(() {});
});
},
)
: const SizedBox(),
Image.asset(
"assets/sheild-front-gradient.png",
width: 200,
height: 200,
), ),
const SizedBox(height: 12),
const Text(
"ente",
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: 'Montserrat',
fontSize: 42,
),
),
const SizedBox(height: 4),
Text(
"Authenticator",
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 32),
Text(
l10n.onBoardingBody,
textAlign: TextAlign.center,
style:
Theme.of(context).textTheme.titleLarge!.copyWith(
color: Colors.white38,
),
),
],
),
const SizedBox(height: 100),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: GradientButton(
onTap: _navigateToSignUpPage,
text: l10n.newUser,
), ),
const SizedBox(height: 16), ),
Container( const SizedBox(height: 4),
height: 56, Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
child: Hero( child: Hero(
tag: "log_in", tag: "log_in",
child: ElevatedButton( child: ElevatedButton(
style: Theme.of(context) style: Theme.of(context)
.colorScheme .colorScheme
.optionalActionButtonStyle, .optionalActionButtonStyle,
onPressed: _navigateToSignInPage, onPressed: _navigateToSignInPage,
child: Text( child: Text(
l10n.existingUser, l10n.existingUser,
style: const TextStyle( style: const TextStyle(
color: Colors.black, // same for both themes color: Colors.black, // same for both themes
),
), ),
), ),
), ),
), ),
const SizedBox(height: 4), ),
Container( const SizedBox(height: 4),
width: double.infinity, Container(
padding: const EdgeInsets.only(top: 20, bottom: 20), width: double.infinity,
child: GestureDetector( padding: const EdgeInsets.only(top: 20, bottom: 20),
onTap: _optForOfflineMode, child: GestureDetector(
child: Center( onTap: _optForOfflineMode,
child: Text( child: Center(
l10n.useOffline, child: Text(
style: body.copyWith( l10n.useOffline,
color: Theme.of(context) style: body.copyWith(
.colorScheme color:
.mutedTextColor, Theme.of(context).colorScheme.mutedTextColor,
),
), ),
), ),
), ),
), ),
const DeveloperSettingsWidget(), ),
], const DeveloperSettingsWidget(),
), ],
), ),
), ),
), ),
@@ -219,9 +208,7 @@ class _OnboardingPageState extends State<OnboardingPage> {
} }
Future<void> _optForOfflineMode() async { Future<void> _optForOfflineMode() async {
bool canCheckBio = Platform.isMacOS || Platform.isLinux bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
? true
: await LocalAuthentication().canCheckBiometrics;
if (!canCheckBio) { if (!canCheckBio) {
showToast( showToast(
context, context,

View File

@@ -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, super.key}); SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: 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?.secret, text: widget.code != null ? widget.code!.secret : null,
); );
_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: Center( body: SafeArea(
child: SingleChildScrollView( child: Center(
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(

View File

@@ -1,3 +1,4 @@
import 'dart:math'; import 'dart:math';
import "package:ente_auth/l10n/l10n.dart"; import "package:ente_auth/l10n/l10n.dart";
@@ -9,7 +10,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, super.key}); ViewQrPage({this.code, Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -21,22 +22,15 @@ class ViewQrPage extends StatelessWidget {
appBar: AppBar( appBar: AppBar(
title: Text(l10n.qrCode), title: Text(l10n.qrCode),
), ),
body: Center( body: SafeArea(
child: SingleChildScrollView( child: Center(
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: [
QrImageView( QrImage(
data: code!.rawData, data: code!.rawData,
eyeStyle: QrEyeStyle( foregroundColor: Theme.of(context).colorScheme.onBackground,
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,
), ),

View File

@@ -15,8 +15,9 @@ 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_crypto_dart/ente_crypto_dart.dart'; import 'package:ente_auth/utils/crypto_util.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';
@@ -74,10 +75,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.decryptData( final decryptedValue = await CryptoUtil.decryptChaCha(
CryptoUtil.base642bin(e.encryptedData), Sodium.base642bin(e.encryptedData),
key, key,
CryptoUtil.base642bin(e.header), Sodium.base642bin(e.header),
); );
final hasSynced = !(e.id == null || e.shouldSync); final hasSynced = !(e.id == null || e.shouldSync);
entities.add( entities.add(
@@ -100,13 +101,12 @@ class AuthenticatorService {
AccountMode accountMode, AccountMode accountMode,
) async { ) async {
var key = await getOrCreateAuthDataKey(accountMode); var key = await getOrCreateAuthDataKey(accountMode);
final encryptedKeyData = await CryptoUtil.encryptData( final encryptedKeyData = await CryptoUtil.encryptChaCha(
utf8.encode(plainText), utf8.encode(plainText) as Uint8List,
key, key,
); );
String encryptedData = String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); String header = Sodium.bin2base64(encryptedKeyData.header!);
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,13 +123,12 @@ class AuthenticatorService {
AccountMode accountMode, AccountMode accountMode,
) async { ) async {
var key = await getOrCreateAuthDataKey(accountMode); var key = await getOrCreateAuthDataKey(accountMode);
final encryptedKeyData = await CryptoUtil.encryptData( final encryptedKeyData = await CryptoUtil.encryptChaCha(
utf8.encode(plainText), utf8.encode(plainText) as Uint8List,
key, key,
); );
String encryptedData = String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); String header = Sodium.bin2base64(encryptedKeyData.header!);
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);
@@ -192,25 +191,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"); _logger.info("Current sync is " + lastSyncTime.toString());
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} entries fetched from remote"); _logger.info(result.length.toString() + " 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} entries deleted"); _logger.info(deletedIDs.length.toString() + " 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"); _logger.info("Setting synctime to " + maxSyncTime.toString());
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();
@@ -224,7 +223,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} entries to be updated at remote", pendingUpdate.length.toString() + " entries to be updated at remote",
); );
for (LocalAuthEntity entity in pendingUpdate) { for (LocalAuthEntity entity in pendingUpdate) {
if (entity.id == null) { if (entity.id == null) {
@@ -263,21 +262,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(
CryptoUtil.base642bin(response.encryptedKey), Sodium.base642bin(response.encryptedKey),
_config.getKey()!, _config.getKey()!,
CryptoUtil.base642bin(response.header), Sodium.base642bin(response.header),
); );
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey)); await _config.setAuthSecretKey(Sodium.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(
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), Sodium.bin2base64(encryptedKeyData.encryptedData!),
CryptoUtil.bin2base64(encryptedKeyData.nonce!), Sodium.bin2base64(encryptedKeyData.nonce!),
); );
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key)); await _config.setAuthSecretKey(Sodium.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);

View File

@@ -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 DioException catch (e) { } on DioError 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 DioException catch (e, s) { } on DioError 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 DioException catch (e, s) { } on DioError 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 DioException catch (e, s) { } on DioError 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 DioException catch (e, s) { } on DioError catch (e, s) {
_logger.severe(e, s); _logger.severe(e, s);
rethrow; rethrow;
} }

View File

@@ -1,21 +1,15 @@
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,
@@ -44,7 +38,7 @@ class LocalAuthenticationService {
String errorDialogContent, [ String errorDialogContent, [
String errorDialogTitle = "", String errorDialogTitle = "",
]) async { ]) async {
if (await _isLocalAuthSupportedOnDevice()) { if (await LocalAuthentication().isDeviceSupported()) {
AppLock.of(context)!.disable(); AppLock.of(context)!.disable();
final result = await requestAuthentication( final result = await requestAuthentication(
context, context,
@@ -71,12 +65,6 @@ class LocalAuthenticationService {
} }
Future<bool> _isLocalAuthSupportedOnDevice() async { Future<bool> _isLocalAuthSupportedOnDevice() async {
try { return await LocalAuthentication().isDeviceSupported();
return Platform.isMacOS || Platform.isLinux
? await FlutterLocalAuthentication().canAuthenticate()
: await LocalAuthentication().isDeviceSupported();
} on MissingPluginException {
return false;
}
} }
} }

View File

@@ -27,7 +27,8 @@ class NotificationService {
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>(); AndroidFlutterLocalNotificationsPlugin>();
if (implementation != null) { if (implementation != null) {
await implementation.requestNotificationsPermission(); // ignore: unawaited_futures
implementation.requestPermission();
} }
} }

View File

@@ -4,7 +4,6 @@ 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';
@@ -131,8 +130,7 @@ class UpdateService {
bool isIndependent() { bool isIndependent() {
return flavor == "independent" || return flavor == "independent" ||
_packageInfo.packageName.endsWith("independent") || _packageInfo.packageName.endsWith("independent");
PlatformUtil.isDesktop();
} }
} }
@@ -143,7 +141,6 @@ 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;
@@ -154,7 +151,6 @@ class LatestVersionInfo {
this.shouldForceUpdate, this.shouldForceUpdate,
this.lastSupportedVersionCode, this.lastSupportedVersionCode,
this.url, this.url,
this.release,
this.size, this.size,
this.shouldNotify, this.shouldNotify,
); );
@@ -167,7 +163,6 @@ 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'],
); );

View File

@@ -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,

View File

@@ -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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError catch (e) {
_logger.info(e); _logger.info(e);
rethrow; rethrow;
} }
@@ -187,7 +187,7 @@ class UserService {
"token": token, "token": token,
}, },
); );
} on DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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), utf8.encode(userPassword) as Uint8List,
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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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 DioException catch (e) { } on DioError 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) {

View File

@@ -1,36 +0,0 @@
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,
);
}
}

View File

@@ -3,12 +3,10 @@ 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";
@@ -27,16 +25,6 @@ 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);
@@ -178,7 +166,7 @@ class AuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]); batch.delete(entityTable, where: whereID, whereArgs: [id]);
} }
} }
final _ = await batch.commit(); await batch.commit();
debugPrint("Done"); debugPrint("Done");
} }

View File

@@ -3,11 +3,10 @@ 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_common_ffi/sqflite_ffi.dart'; import 'package:sqflite/sqflite.dart';
class OfflineAuthenticatorDB { class OfflineAuthenticatorDB {
static const _databaseName = "ente.offline_authenticator.db"; static const _databaseName = "ente.offline_authenticator.db";
@@ -27,16 +26,6 @@ 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);
@@ -163,7 +152,7 @@ class OfflineAuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]); batch.delete(entityTable, where: whereID, whereArgs: [id]);
} }
} }
final _ = await batch.commit(); await batch.commit();
debugPrint("Done"); debugPrint("Done");
} }

View File

@@ -204,7 +204,6 @@ 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);

View File

@@ -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({super.key}); const ChangeEmailDialog({Key? key}) : super(key: key);
@override @override
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState(); State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();

View File

@@ -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({
super.key, Key? key,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -150,8 +150,6 @@ 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,
@@ -166,10 +164,8 @@ class DeleteAccountPage extends StatelessWidget {
return; return;
} }
final decryptChallenge = CryptoUtil.openSealSync( final decryptChallenge = CryptoUtil.openSealSync(
CryptoUtil.base642bin(response.encryptedChallenge), Sodium.base642bin(response.encryptedChallenge),
CryptoUtil.base642bin( Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
Configuration.instance.getKeyAttributes()!.publicKey,
),
Configuration.instance.getSecretKey()!, Configuration.instance.getSecretKey()!,
); );
final challengeResponseStr = utf8.decode(decryptChallenge); final challengeResponseStr = utf8.decode(decryptChallenge);

View File

@@ -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/utils/platform_util.dart'; import 'package:ente_auth/ui/common/web_page.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({super.key}); const EmailEntryPage({Key? key}) : super(key: key);
@override @override
State<EmailEntryPage> createState() => _EmailEntryPageState(); State<EmailEntryPage> createState() => _EmailEntryPageState();
@@ -190,7 +190,6 @@ 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,
@@ -428,10 +427,15 @@ 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) =>
PlatformUtil.openWebView( Navigator.of(context).push(
context, MaterialPageRoute(
context.l10n.termsOfServicesTitle, builder: (BuildContext context) {
"https://ente.io/terms", return WebPage(
context.l10n.termsOfServicesTitle,
"https://ente.io/terms",
);
},
),
), ),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
@@ -439,10 +443,15 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
), ),
'u-policy': StyledTextActionTag( 'u-policy': StyledTextActionTag(
(String? text, Map<String?, String?> attrs) => (String? text, Map<String?, String?> attrs) =>
PlatformUtil.openWebView( Navigator.of(context).push(
context, MaterialPageRoute(
context.l10n.privacyPolicyTitle, builder: (BuildContext context) {
"https://ente.io/privacy", return WebPage(
context.l10n.privacyPolicyTitle,
"https://ente.io/privacy",
);
},
),
), ),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
@@ -485,10 +494,15 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
tags: { tags: {
'underline': StyledTextActionTag( 'underline': StyledTextActionTag(
(String? text, Map<String?, String?> attrs) => (String? text, Map<String?, String?> attrs) =>
PlatformUtil.openWebView( Navigator.of(context).push(
context, MaterialPageRoute(
context.l10n.encryption, builder: (BuildContext context) {
"https://ente.io/architecture", return WebPage(
context.l10n.encryption,
"https://ente.io/architecture",
);
},
),
), ),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,

View File

@@ -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/utils/platform_util.dart'; import 'package:ente_auth/ui/common/web_page.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({super.key}); const LoginPage({Key? key}) : super(key: key);
@override @override
State<LoginPage> createState() => _LoginPageState(); State<LoginPage> createState() => _LoginPageState();
@@ -25,36 +25,6 @@ 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();
@@ -90,7 +60,36 @@ class _LoginPageState extends State<LoginPage> {
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _emailIsValid, isFormValid: _emailIsValid,
buttonText: context.l10n.logInLabel, buttonText: context.l10n.logInLabel,
onPressedFunction: onPressed, onPressedFunction: () async {
await UserService.instance.setEmail(_email!);
Configuration.instance.resetVolatilePassword();
SrpAttributes? attr;
bool isEmailVerificationEnabled = true;
try {
attr = await UserService.instance.getSrpAttributes(_email!);
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
} catch (e) {
if (e is! SrpSetupNotCompleteError) {
_logger.severe('Error getting SRP attributes', e);
}
}
if (attr != null && !isEmailVerificationEnabled) {
// ignore: unawaited_futures
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return LoginPasswordVerificationPage(
srpAttributes: attr!,
);
},
),
);
} else {
await UserService.instance
.sendOtt(context, _email!, isCreateAccountScreen: false);
}
FocusScope.of(context).unfocus();
},
), ),
floatingActionButtonLocation: fabLocation(), floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(), floatingActionButtonAnimator: NoScalingAnimation(),
@@ -117,8 +116,6 @@ 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,
@@ -182,10 +179,15 @@ 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) =>
PlatformUtil.openWebView( Navigator.of(context).push(
context, MaterialPageRoute(
context.l10n.termsOfServicesTitle, builder: (BuildContext context) {
"https://ente.io/terms", return WebPage(
context.l10n.termsOfServicesTitle,
"https://ente.io/terms",
);
},
),
), ),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,
@@ -193,10 +195,15 @@ class _LoginPageState extends State<LoginPage> {
), ),
'u-policy': StyledTextActionTag( 'u-policy': StyledTextActionTag(
(String? text, Map<String?, String?> attrs) => (String? text, Map<String?, String?> attrs) =>
PlatformUtil.openWebView( Navigator.of(context).push(
context, MaterialPageRoute(
context.l10n.privacyPolicyTitle, builder: (BuildContext context) {
"https://ente.io/privacy", return WebPage(
context.l10n.privacyPolicyTitle,
"https://ente.io/privacy",
);
},
),
), ),
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, decoration: TextDecoration.underline,

View File

@@ -1,5 +1,6 @@
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";
@@ -8,7 +9,6 @@ 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,7 +19,8 @@ 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({super.key, required this.srpAttributes}); const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
: super(key: key);
@override @override
State<LoginPasswordVerificationPage> createState() => State<LoginPasswordVerificationPage> createState() =>
@@ -35,11 +36,6 @@ 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();
@@ -81,7 +77,10 @@ class _LoginPasswordVerificationPageState
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _passwordController.text.isNotEmpty, isFormValid: _passwordController.text.isNotEmpty,
buttonText: context.l10n.logInLabel, buttonText: context.l10n.logInLabel,
onPressedFunction: onPressed, onPressedFunction: () async {
FocusScope.of(context).unfocus();
await verifyPassword(context, _passwordController.text);
},
), ),
floatingActionButtonLocation: fabLocation(), floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(), floatingActionButtonAnimator: NoScalingAnimation(),
@@ -102,7 +101,7 @@ class _LoginPasswordVerificationPageState
password, password,
dialog, dialog,
); );
} on DioException catch (e, s) { } on DioError 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);
@@ -113,7 +112,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 == DioExceptionType.unknown) { if (e.type == DioErrorType.other) {
await _showContactSupportDialog( await _showContactSupportDialog(
context, context,
context.l10n.noInternetConnection, context.l10n.noInternetConnection,
@@ -230,9 +229,6 @@ 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(

View File

@@ -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,
super.key, Key? key,
}); }) : super(key: key);
@override @override
State<OTTVerificationPage> createState() => _OTTVerificationPageState(); State<OTTVerificationPage> createState() => _OTTVerificationPageState();
@@ -27,23 +27,6 @@ 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;
@@ -85,9 +68,22 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
body: _getBody(), body: _getBody(),
floatingActionButton: DynamicFAB( floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _verificationCodeController.text.isNotEmpty, isFormValid: !(_verificationCodeController.text.isEmpty),
buttonText: l10n.verify, buttonText: l10n.verify,
onPressedFunction: onPressed, onPressedFunction: () {
if (widget.isChangeEmail) {
UserService.instance.changeEmail(
context,
widget.email,
_verificationCodeController.text,
);
} else {
UserService.instance
.verifyEmail(context, _verificationCodeController.text,
isResettingPasswordScreen: widget.isResetPasswordScreen,);
}
FocusScope.of(context).unfocus();
},
), ),
floatingActionButtonLocation: fabLocation(), floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(), floatingActionButtonAnimator: NoScalingAnimation(),
@@ -164,9 +160,6 @@ 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,

View File

@@ -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, super.key}); const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
@override @override
State<PasswordEntryPage> createState() => _PasswordEntryPageState(); State<PasswordEntryPage> createState() => _PasswordEntryPageState();
@@ -149,239 +149,227 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
children: [ children: [
Expanded( Expanded(
child: AutofillGroup( child: AutofillGroup(
child: FocusTraversalGroup( child: ListView(
policy: OrderedTraversalPolicy(), children: [
child: ListView( Padding(
children: [ padding:
Padding( const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
padding: const EdgeInsets.symmetric( child: Text(
vertical: 30, buttonTextAndHeading,
horizontal: 20, style: Theme.of(context).textTheme.headlineMedium,
),
child: Text(
buttonTextAndHeading,
style: Theme.of(context).textTheme.headlineMedium,
),
), ),
Padding( ),
padding: const EdgeInsets.symmetric(horizontal: 20), Padding(
child: Text( padding: const EdgeInsets.symmetric(horizontal: 20),
widget.mode == PasswordEntryMode.set child: Text(
? context.l10n.enterPasswordToEncrypt widget.mode == PasswordEntryMode.set
: context.l10n.enterNewPasswordToEncrypt, ? context.l10n.enterPasswordToEncrypt
textAlign: TextAlign.start, : context.l10n.enterNewPasswordToEncrypt,
style: Theme.of(context) textAlign: TextAlign.start,
.textTheme style: Theme.of(context)
.titleMedium! .textTheme
.copyWith(fontSize: 14), .titleMedium!
), .copyWith(fontSize: 14),
), ),
const Padding(padding: EdgeInsets.all(8)), ),
Padding( const Padding(padding: EdgeInsets.all(8)),
padding: const EdgeInsets.symmetric(horizontal: 20), Padding(
child: StyledText( padding: const EdgeInsets.symmetric(horizontal: 20),
text: context.l10n.passwordWarning, child: StyledText(
style: Theme.of(context) text: context.l10n.passwordWarning,
.textTheme style: Theme.of(context)
.titleMedium! .textTheme
.copyWith(fontSize: 14), .titleMedium!
tags: { .copyWith(fontSize: 14),
'underline': StyledTextTag( tags: {
style: 'underline': StyledTextTag(
Theme.of(context).textTheme.titleMedium!.copyWith( style:
fontSize: 14, Theme.of(context).textTheme.titleMedium!.copyWith(
decoration: TextDecoration.underline, fontSize: 14,
), decoration: TextDecoration.underline,
),
},
),
),
const Padding(padding: EdgeInsets.all(12)),
Visibility(
// hidden textForm for suggesting auto-fill service for saving
// password
visible: false,
child: TextFormField(
autofillHints: const [
AutofillHints.email,
],
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: email,
textInputAction: TextInputAction.next,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
autofillHints: const [AutofillHints.newPassword],
onFieldSubmitted: (_) {
do {
FocusScope.of(context).nextFocus();
} while (FocusScope.of(context).focusedChild!.context ==
null);
},
decoration: InputDecoration(
fillColor:
_isPasswordValid ? _validFieldValueColor : null,
filled: true,
hintText: context.l10n.password,
contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
), ),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
: _isPasswordValid
? Icon(
Icons.check,
color: Theme.of(context)
.inputDecorationTheme
.focusedBorder!
.borderSide
.color,
)
: null,
), ),
obscureText: !_password1Visible, },
controller: _passwordController1, ),
autofocus: false, ),
autocorrect: false, const Padding(padding: EdgeInsets.all(12)),
keyboardType: TextInputType.visiblePassword, Visibility(
onChanged: (password) { // hidden textForm for suggesting auto-fill service for saving
setState(() { // password
_passwordInInputBox = password; visible: false,
_passwordStrength = child: TextFormField(
estimatePasswordStrength(password); autofillHints: const [
_isPasswordValid = _passwordStrength >= AutofillHints.email,
kMildPasswordStrengthThreshold; ],
autocorrect: false,
keyboardType: TextInputType.emailAddress,
initialValue: email,
textInputAction: TextInputAction.next,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
autofillHints: const [AutofillHints.newPassword],
decoration: InputDecoration(
fillColor:
_isPasswordValid ? _validFieldValueColor : null,
filled: true,
hintText: context.l10n.password,
contentPadding: const EdgeInsets.all(20),
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
suffixIcon: _password1InFocus
? IconButton(
icon: Icon(
_password1Visible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_password1Visible = !_password1Visible;
});
},
)
: _isPasswordValid
? Icon(
Icons.check,
color: Theme.of(context)
.inputDecorationTheme
.focusedBorder!
.borderSide
.color,
)
: null,
),
obscureText: !_password1Visible,
controller: _passwordController1,
autofocus: false,
autocorrect: false,
keyboardType: TextInputType.visiblePassword,
onChanged: (password) {
setState(() {
_passwordInInputBox = password;
_passwordStrength = estimatePasswordStrength(password);
_isPasswordValid =
_passwordStrength >= kMildPasswordStrengthThreshold;
_passwordsMatch = _passwordInInputBox ==
_passwordInInputConfirmationBox;
});
},
textInputAction: TextInputAction.next,
focusNode: _password1FocusNode,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: _passwordController2,
obscureText: !_password2Visible,
autofillHints: const [AutofillHints.newPassword],
onEditingComplete: () => TextInput.finishAutofillContext(),
decoration: InputDecoration(
fillColor: _passwordsMatch ? _validFieldValueColor : null,
filled: true,
hintText: context.l10n.confirmPassword,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 20,
),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: _passwordsMatch
? Icon(
Icons.check,
color: Theme.of(context)
.inputDecorationTheme
.focusedBorder!
.borderSide
.color,
)
: null,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
focusNode: _password2FocusNode,
onChanged: (cnfPassword) {
setState(() {
_passwordInInputConfirmationBox = cnfPassword;
if (_passwordInInputBox != '') {
_passwordsMatch = _passwordInInputBox == _passwordsMatch = _passwordInInputBox ==
_passwordInInputConfirmationBox; _passwordInInputConfirmationBox;
}); }
}, });
textInputAction: TextInputAction.next,
focusNode: _password1FocusNode,
),
),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: TextFormField(
keyboardType: TextInputType.visiblePassword,
controller: _passwordController2,
obscureText: !_password2Visible,
autofillHints: const [AutofillHints.newPassword],
onEditingComplete: () =>
TextInput.finishAutofillContext(),
decoration: InputDecoration(
fillColor:
_passwordsMatch ? _validFieldValueColor : null,
filled: true,
hintText: context.l10n.confirmPassword,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 20,
),
suffixIcon: _password2InFocus
? IconButton(
icon: Icon(
_password2Visible
? Icons.visibility
: Icons.visibility_off,
color: Theme.of(context).iconTheme.color,
size: 20,
),
onPressed: () {
setState(() {
_password2Visible = !_password2Visible;
});
},
)
: _passwordsMatch
? Icon(
Icons.check,
color: Theme.of(context)
.inputDecorationTheme
.focusedBorder!
.borderSide
.color,
)
: null,
border: UnderlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(6),
),
),
focusNode: _password2FocusNode,
onChanged: (cnfPassword) {
setState(() {
_passwordInInputConfirmationBox = cnfPassword;
if (_passwordInInputBox != '') {
_passwordsMatch = _passwordInInputBox ==
_passwordInInputConfirmationBox;
}
});
},
),
),
Opacity(
opacity: (_passwordInInputBox != '') && _password1InFocus
? 1
: 0,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
child: Text(
context.l10n.passwordStrength(passwordStrengthText),
style: TextStyle(
color: passwordStrengthColor,
),
),
),
),
const SizedBox(height: 8),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
PlatformUtil.openWebView(
context,
context.l10n.howItWorks,
"https://ente.io/architecture",
);
}, },
child: Container( ),
padding: const EdgeInsets.symmetric(horizontal: 20), ),
child: RichText( Opacity(
text: TextSpan( opacity:
text: context.l10n.howItWorks, (_passwordInInputBox != '') && _password1InFocus ? 1 : 0,
style: child: Padding(
Theme.of(context).textTheme.titleMedium!.copyWith( padding:
fontSize: 14, const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: TextDecoration.underline, child: Text(
), context.l10n.passwordStrength(passwordStrengthText),
), style: TextStyle(
color: passwordStrengthColor,
), ),
), ),
), ),
const Padding(padding: EdgeInsets.all(20)), ),
], const SizedBox(height: 8),
), GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) {
return WebPage(
context.l10n.howItWorks,
"https://ente.io/architecture",
);
},
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: RichText(
text: TextSpan(
text: context.l10n.howItWorks,
style:
Theme.of(context).textTheme.titleMedium!.copyWith(
fontSize: 14,
decoration: TextDecoration.underline,
),
),
),
),
),
const Padding(padding: EdgeInsets.all(20)),
],
), ),
), ),
), ),
@@ -475,7 +463,6 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
showGenericErrorDialog(context: context); showGenericErrorDialog(context: context);
} }
} }
// ignore: unawaited_futures // ignore: unawaited_futures
routeToPage( routeToPage(
context, context,

View File

@@ -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({super.key}); const PasswordReentryPage({Key? key}) : super(key: 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: Row( child: Wrap(
mainAxisAlignment: MainAxisAlignment.spaceBetween, alignment: WrapAlignment.spaceBetween,
children: [ children: [
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@@ -275,17 +275,13 @@ 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 fontSize: 14,
.titleMedium! decoration: TextDecoration.underline,
.copyWith( ),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
), ),
), ),
GestureDetector( GestureDetector(
@@ -301,17 +297,13 @@ 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 fontSize: 14,
.titleMedium! decoration: TextDecoration.underline,
.copyWith( ),
fontSize: 14,
decoration: TextDecoration.underline,
),
),
), ),
), ),
], ],

View File

@@ -1,4 +1,3 @@
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;
@@ -8,10 +7,7 @@ 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';
@@ -31,7 +27,7 @@ class RecoveryKeyPage extends StatefulWidget {
const RecoveryKeyPage( const RecoveryKeyPage(
this.recoveryKey, this.recoveryKey,
this.doneText, { this.doneText, {
super.key, Key? key,
this.showAppBar, this.showAppBar,
this.onDone, this.onDone,
this.isDismissible, this.isDismissible,
@@ -39,7 +35,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();
@@ -48,7 +44,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
@@ -65,21 +61,6 @@ 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(
@@ -132,73 +113,62 @@ 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)),
Container( DottedBorder(
padding: const EdgeInsets.all(1), color: const Color.fromARGB(255, 105, 17, 127),
decoration: BoxDecoration( //color of dotted/dash line
borderRadius: BorderRadius.circular(8), strokeWidth: 1,
gradient: const LinearGradient( //thickness of dash/dots
begin: Alignment.topCenter, dashPattern: const [6, 6],
end: Alignment.bottomCenter, radius: const Radius.circular(8),
colors: [ //dash patterns, 10 is dash width, 6 is space width
Color(0x8E9610D6), child: SizedBox(
Color(0x8E9F4FC6), //inner container
], // height: 120, //height of inner container
stops: [0.0, 0.9753], width: double
), .infinity, //width to 100% match to parent container.
), // ignore: prefer_const_literals_to_create_immutables
child: DottedBorder( child: Column(
padding: EdgeInsets.zero, children: [
borderType: BorderType.RRect, GestureDetector(
strokeWidth: 1, onTap: () async {
color: const Color(0xFF6B6B6B), await Clipboard.setData(
dashPattern: const [6, 6], ClipboardData(text: recoveryKey),
radius: const Radius.circular(8), );
child: SizedBox( showShortToast(
width: double.infinity, context,
child: Stack( context.l10n.recoveryKeyCopiedToClipboard,
children: [ );
Column( setState(() {
children: [ _hasTriedToSave = true;
Builder( });
builder: (context) { },
final content = Container( child: Container(
padding: const EdgeInsets.all(20), decoration: BoxDecoration(
width: double.infinity, border: Border.all(
child: Text( color: const Color.fromRGBO(
recoveryKey, 49,
textAlign: TextAlign.justify, 155,
style: Theme.of(context) 86,
.textTheme .2,
.bodyLarge, ),
),
);
if (PlatformUtil.isMobile()) {
return GestureDetector(
onTap: () async => await copy(),
child: content,
);
} else {
return SelectableRegion(
focusNode: FocusNode(),
selectionControls:
PlatformUtil.selectionControls,
child: content,
);
}
},
), ),
], borderRadius: const BorderRadius.all(
), Radius.circular(2),
Positioned( ),
right: 0, color: Theme.of(context)
top: 0, .colorScheme
child: PlatformCopy( .recoveryKeyBoxColor,
onPressed: copy, ),
padding: const EdgeInsets.all(20),
width: double.infinity,
child: Text(
recoveryKey,
style:
Theme.of(context).textTheme.bodyLarge,
), ),
), ),
], ),
), ],
), ),
), ),
), ),
@@ -223,7 +193,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
), ),
), ),
], ],
), ), // columnEnds
), ),
), ),
); );
@@ -237,15 +207,12 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
final List<Widget> childrens = []; final List<Widget> childrens = [];
if (!_hasTriedToSave) { if (!_hasTriedToSave) {
childrens.add( childrens.add(
SizedBox( ElevatedButton(
height: 56, style: Theme.of(context).colorScheme.optionalActionButtonStyle,
child: ElevatedButton( onPressed: () async {
style: Theme.of(context).colorScheme.optionalActionButtonStyle, await _saveKeys();
onPressed: () async { },
await _saveKeys(); child: Text(context.l10n.doThisLater),
},
child: Text(context.l10n.doThisLater),
),
), ),
); );
childrens.add(const SizedBox(height: 10)); childrens.add(const SizedBox(height: 10));
@@ -254,32 +221,19 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
childrens.add( childrens.add(
GradientButton( GradientButton(
onTap: () async { onTap: () async {
await shareDialog( await _shareRecoveryKey(recoveryKey);
context,
context.l10n.recoveryKey,
saveAction: () async {
await _saveRecoveryKey(recoveryKey);
},
sendAction: () async {
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(
SizedBox( ElevatedButton(
height: 56, child: Text(widget.doneText),
child: ElevatedButton( onPressed: () async {
child: Text(widget.doneText), await _saveKeys();
onPressed: () async { },
await _saveKeys();
},
),
), ),
); );
} }
@@ -287,34 +241,11 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
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) {
@@ -333,24 +264,3 @@ 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,
),
);
}
}

View File

@@ -1,5 +1,8 @@
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';
@@ -7,7 +10,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({super.key}); const RecoveryPage({Key? key}) : super(key: key);
@override @override
State<RecoveryPage> createState() => _RecoveryPageState(); State<RecoveryPage> createState() => _RecoveryPageState();
@@ -16,36 +19,6 @@ 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;
@@ -73,7 +46,37 @@ class _RecoveryPageState extends State<RecoveryPage> {
isKeypadOpen: isKeypadOpen, isKeypadOpen: isKeypadOpen,
isFormValid: _recoveryKey.text.isNotEmpty, isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover', buttonText: 'Recover',
onPressedFunction: onPressed, onPressedFunction: () async {
FocusScope.of(context).unfocus();
final dialog = createProgressDialog(context, "Decrypting...");
await dialog.show();
try {
await Configuration.instance.recover(_recoveryKey.text.trim());
await dialog.hide();
showToast(context, "Recovery successful!");
// ignore: unawaited_futures
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: const PasswordEntryPage(
mode: PasswordEntryMode.reset,
),
);
},
),
);
} catch (e) {
await dialog.hide();
String errMessage = 'The recovery key you entered is incorrect';
if (e is AssertionError) {
errMessage = '$errMessage : ${e.message}';
}
// ignore: unawaited_futures
showErrorDialog(context, "Incorrect recovery key", errMessage);
}
},
), ),
floatingActionButtonLocation: fabLocation(), floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(), floatingActionButtonAnimator: NoScalingAnimation(),
@@ -86,7 +89,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(
context.l10n.forgotPassword, 'Forgot password',
style: Theme.of(context).textTheme.headlineMedium, style: Theme.of(context).textTheme.headlineMedium,
), ),
), ),
@@ -137,14 +140,12 @@ 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(
context.l10n.noRecoveryKeyTitle, "No recovery key?",
style: Theme.of(context) style:
.textTheme Theme.of(context).textTheme.titleMedium!.copyWith(
.titleMedium! fontSize: 14,
.copyWith( decoration: TextDecoration.underline,
fontSize: 14, ),
decoration: TextDecoration.underline,
),
), ),
), ),
), ),

View File

@@ -5,9 +5,10 @@ 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);
@@ -16,11 +17,8 @@ class RequestPasswordVerificationPage extends StatefulWidget {
final OnPasswordVerifiedFn onPasswordVerified; final OnPasswordVerifiedFn onPasswordVerified;
final Function? onPasswordError; final Function? onPasswordError;
const RequestPasswordVerificationPage({ const RequestPasswordVerificationPage(
super.key, {super.key, required this.onPasswordVerified, this.onPasswordError,});
required this.onPasswordVerified,
this.onPasswordError,
});
@override @override
State<RequestPasswordVerificationPage> createState() => State<RequestPasswordVerificationPage> createState() =>
@@ -84,15 +82,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), utf8.encode(_passwordController.text) as Uint8List,
CryptoUtil.base642bin(attributes.kekSalt), Sodium.base642bin(attributes.kekSalt),
attributes.memLimit, attributes.memLimit,
attributes.opsLimit, attributes.opsLimit,
); );
CryptoUtil.decryptSync( CryptoUtil.decryptSync(
CryptoUtil.base642bin(attributes.encryptedKey), Sodium.base642bin(attributes.encryptedKey),
keyEncryptionKey, keyEncryptionKey,
CryptoUtil.base642bin(attributes.keyDecryptionNonce), Sodium.base642bin(attributes.keyDecryptionNonce),
); );
await dialog.show(); await dialog.show();
// pop // pop

View File

@@ -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({super.key}); const SessionsPage({Key? key}) : super(key: key);
@override @override
State<SessionsPage> createState() => _SessionsPageState(); State<SessionsPage> createState() => _SessionsPageState();

View File

@@ -1,3 +1,5 @@
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';
@@ -10,13 +12,12 @@ 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({super.key}); const VerifyRecoveryPage({Key? key}) : super(key: key);
@override @override
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState(); State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
@@ -33,14 +34,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 =
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); Sodium.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 DioException && e.type == DioExceptionType.unknown) { if (e is DioError && e.type == DioErrorType.other) {
await showErrorDialog( await showErrorDialog(
context, context,
"No internet connection", "No internet connection",
@@ -87,14 +88,12 @@ 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 = recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); // ignore: unawaited_futures
await routeToPage( routeToPage(
context, context,
RecoveryKeyPage( RecoveryKeyPage(
recoveryKey, recoveryKey,

View File

@@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
final int period; final int period;
CodeTimerProgress({ CodeTimerProgress({
super.key, Key? key,
required this.period, required this.period,
}); }) : super(key: key);
@override @override
State createState() => _CodeTimerProgressState(); _CodeTimerProgressState createState() => _CodeTimerProgressState();
} }
class _CodeTimerProgressState extends State<CodeTimerProgress> class _CodeTimerProgressState extends State<CodeTimerProgress>

View File

@@ -14,10 +14,8 @@ 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/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
@@ -26,7 +24,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, {super.key}); const CodeWidget(this.code, {Key? key}) : super(key: key);
@override @override
State<CodeWidget> createState() => _CodeWidgetState(); State<CodeWidget> createState() => _CodeWidgetState();
@@ -135,59 +133,35 @@ class _CodeWidgetState extends State<CodeWidget> {
), ),
], ],
), ),
child: Builder( child: ClipRRect(
builder: (context) { borderRadius: BorderRadius.circular(8),
return RawGestureDetector( child: Container(
gestures: { color: Theme.of(context).colorScheme.codeCardBackgroundColor,
PanGestureRecognizer: child: Material(
GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( color: Colors.transparent,
() => PanGestureRecognizer( child: InkWell(
debugOwner: this, customBorder: RoundedRectangleBorder(
// This recognizer accepts any button press made with a secondary button. borderRadius: BorderRadius.circular(10),
allowedButtonsFilter: (int buttons) =>
buttons & kSecondaryButton != 0,
),
(PanGestureRecognizer instance) {
instance
..dragStartBehavior = DragStartBehavior.down
..onEnd = (DragEndDetails details) {
Slidable.of(context)?.openEndActionPane();
};
},
),
},
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Container(
color: Theme.of(context).colorScheme.codeCardBackgroundColor,
child: Material(
color: Colors.transparent,
child: InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
onTap: () {
_copyCurrentOTPToClipboard();
},
onDoubleTap: isMaskingEnabled
? () {
setState(
() {
_hideCode = !_hideCode;
},
);
}
: null,
onLongPress: () {
_copyCurrentOTPToClipboard();
},
child: _getCardContents(l10n),
),
),
), ),
onTap: () {
_copyCurrentOTPToClipboard();
},
onDoubleTap: isMaskingEnabled
? () {
setState(
() {
_hideCode = !_hideCode;
},
);
}
: null,
onLongPress: () {
_copyCurrentOTPToClipboard();
},
child: _getCardContents(l10n),
), ),
); ),
}, ),
), ),
), ),
); );
@@ -399,10 +373,9 @@ 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);
await PlatformUtil.refocusWindows(); if (!_isAuthSuccessful) {
if (!isAuthSuccessful) {
return; return;
} }
final Code? code = await Navigator.of(context).push( final Code? code = await Navigator.of(context).push(
@@ -418,10 +391,9 @@ 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);
await PlatformUtil.refocusWindows(); if (!_isAuthSuccessful) {
if (!isAuthSuccessful) {
return; return;
} }
// ignore: unused_local_variable // ignore: unused_local_variable
@@ -435,15 +407,14 @@ 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,
@@ -480,7 +451,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;
} }

View File

@@ -1,15 +1,17 @@
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({
super.key, Key? 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) {

View File

@@ -5,7 +5,8 @@ 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, super.key}); const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -10,12 +10,12 @@ class DynamicFAB extends StatelessWidget {
final Function? onPressedFunction; final Function? onPressedFunction;
const DynamicFAB({ const DynamicFAB({
super.key, Key? 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,7 +60,6 @@ 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:

View File

@@ -1,3 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class GradientButton extends StatelessWidget { class GradientButton extends StatelessWidget {
@@ -13,21 +15,17 @@ 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({
super.key, Key? 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) {
@@ -73,9 +71,7 @@ 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: reversedGradient colors: linearGradientColors,
? linearGradientColors.reversed.toList()
: linearGradientColors,
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),

View File

@@ -1,10 +1,12 @@
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, {super.key}); const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
@override @override
LinearProgressDialogState createState() => LinearProgressDialogState(); LinearProgressDialogState createState() => LinearProgressDialogState();
@@ -27,8 +29,8 @@ class LinearProgressDialogState extends State<LinearProgressDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return PopScope( return WillPopScope(
canPop: false, onWillPop: () async => false,
child: AlertDialog( child: AlertDialog(
title: Text( title: Text(
widget.message, widget.message,

View File

@@ -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,
super.key, Key? key,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -153,8 +153,8 @@ class ProgressDialog {
barrierColor: _barrierColor, barrierColor: _barrierColor,
builder: (BuildContext context) { builder: (BuildContext context) {
_dismissingContext = context; _dismissingContext = context;
return PopScope( return WillPopScope(
canPop: _barrierDismissible, onWillPop: () async => _barrierDismissible,
child: Dialog( child: Dialog(
backgroundColor: _backgroundColor, backgroundColor: _backgroundColor,
insetAnimationCurve: _insetAnimCurve, insetAnimationCurve: _insetAnimCurve,
@@ -198,7 +198,6 @@ class _Body extends StatefulWidget {
@override @override
State<StatefulWidget> createState() { State<StatefulWidget> createState() {
// ignore: no_logic_in_create_state
return _dialog; return _dialog;
} }
} }

View File

@@ -8,7 +8,8 @@ class RenameDialog extends StatefulWidget {
final String type; final String type;
final int maxLength; final int maxLength;
const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100}); const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100})
: super(key: key);
@override @override
State<RenameDialog> createState() => _RenameDialogState(); State<RenameDialog> createState() => _RenameDialogState();

View File

@@ -6,7 +6,7 @@ class WebPage extends StatefulWidget {
final String title; final String title;
final String url; final String url;
const WebPage(this.title, this.url, {super.key}); const WebPage(this.title, this.url, {Key? key}) : super(key: key);
@override @override
State<WebPage> createState() => _WebPageState(); State<WebPage> createState() => _WebPageState();
@@ -28,9 +28,9 @@ class _WebPageState extends State<WebPage> {
), ),
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: InAppWebView( body: InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(widget.url)), initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
initialSettings: InAppWebViewSettings( initialOptions: InAppWebViewGroupOptions(
transparentBackground: true, crossPlatform: InAppWebViewOptions(transparentBackground: true),
), ),
onLoadStop: (c, url) { onLoadStop: (c, url) {
setState(() { setState(() {

View File

@@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget {
final ValueNotifier<String>? progressStatus; final ValueNotifier<String>? progressStatus;
const ButtonWidget({ const ButtonWidget({
super.key, Key? key,
required this.buttonType, required this.buttonType,
this.buttonSize = ButtonSize.large, this.buttonSize = ButtonSize.large,
this.icon, this.icon,
@@ -71,7 +71,7 @@ class ButtonWidget extends StatelessWidget {
this.shouldSurfaceExecutionStates = true, this.shouldSurfaceExecutionStates = true,
this.progressStatus, this.progressStatus,
this.shouldShowSuccessConfirmation = false, this.shouldShowSuccessConfirmation = false,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -155,7 +155,7 @@ class ButtonChildWidget extends StatefulWidget {
final bool shouldShowSuccessConfirmation; final bool shouldShowSuccessConfirmation;
const ButtonChildWidget({ const ButtonChildWidget({
super.key, Key? key,
required this.buttonStyle, required this.buttonStyle,
required this.buttonType, required this.buttonType,
required this.isDisabled, required this.isDisabled,
@@ -168,7 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
this.labelText, this.labelText,
this.icon, this.icon,
this.buttonAction, this.buttonAction,
}); }) : super(key: key);
@override @override
State<ButtonChildWidget> createState() => _ButtonChildWidgetState(); State<ButtonChildWidget> createState() => _ButtonChildWidgetState();

View File

@@ -17,7 +17,7 @@ class IconButtonWidget extends StatefulWidget {
final Color? pressedColor; final Color? pressedColor;
final Color? iconColor; final Color? iconColor;
const IconButtonWidget({ const IconButtonWidget({
super.key, Key? key,
required this.icon, required this.icon,
required this.iconButtonType, required this.iconButtonType,
this.disableGestureDetector = false, this.disableGestureDetector = false,
@@ -25,7 +25,7 @@ class IconButtonWidget extends StatefulWidget {
this.defaultColor, this.defaultColor,
this.pressedColor, this.pressedColor,
this.iconColor, this.iconColor,
}); }) : super(key: key);
@override @override
State<IconButtonWidget> createState() => _IconButtonWidgetState(); State<IconButtonWidget> createState() => _IconButtonWidgetState();

View File

@@ -13,8 +13,8 @@ class CaptionedTextWidget extends StatelessWidget {
this.textStyle, this.textStyle,
this.makeTextBold = false, this.makeTextBold = false,
this.textColor, this.textColor,
super.key, Key? key,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -14,12 +14,12 @@ class DividerWidget extends StatelessWidget {
final bool divColorHasBlur; final bool divColorHasBlur;
final EdgeInsets? padding; final EdgeInsets? padding;
const DividerWidget({ const DividerWidget({
super.key, Key? key,
required this.dividerType, required this.dividerType,
this.bgColor = Colors.transparent, this.bgColor = Colors.transparent,
this.divColorHasBlur = true, this.divColorHasBlur = true,
this.padding, this.padding,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -13,8 +13,8 @@ class ExpandableMenuItemWidget extends StatefulWidget {
required this.title, required this.title,
required this.selectionOptionsWidget, required this.selectionOptionsWidget,
required this.leadingIcon, required this.leadingIcon,
super.key, Key? key,
}); }) : super(key: key);
@override @override
State<ExpandableMenuItemWidget> createState() => State<ExpandableMenuItemWidget> createState() =>

View File

@@ -1,10 +1,13 @@
import 'dart:ui';
import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/opened_settings_event.dart'; import 'package:ente_auth/events/opened_settings_event.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class HomeHeaderWidget extends StatefulWidget { class HomeHeaderWidget extends StatefulWidget {
final Widget centerWidget; final Widget centerWidget;
const HomeHeaderWidget({required this.centerWidget, super.key}); const HomeHeaderWidget({required this.centerWidget, Key? key})
: super(key: key);
@override @override
State<HomeHeaderWidget> createState() => _HomeHeaderWidgetState(); State<HomeHeaderWidget> createState() => _HomeHeaderWidgetState();
@@ -13,7 +16,7 @@ class HomeHeaderWidget extends StatefulWidget {
class _HomeHeaderWidgetState extends State<HomeHeaderWidget> { class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final hasNotch = View.of(context).viewPadding.top > 65; final hasNotch = window.viewPadding.top > 65;
return Padding( return Padding(
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4), padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
child: Row( child: Row(

View File

@@ -12,7 +12,7 @@ class TrailingWidget extends StatefulWidget {
final double trailingExtraMargin; final double trailingExtraMargin;
final bool showExecutionStates; final bool showExecutionStates;
const TrailingWidget({ const TrailingWidget({
super.key, Key? key,
required this.executionStateNotifier, required this.executionStateNotifier,
this.trailingIcon, this.trailingIcon,
this.trailingIconColor, this.trailingIconColor,
@@ -20,7 +20,7 @@ class TrailingWidget extends StatefulWidget {
required this.trailingIconIsMuted, required this.trailingIconIsMuted,
required this.trailingExtraMargin, required this.trailingExtraMargin,
required this.showExecutionStates, required this.showExecutionStates,
}); }) : super(key: key);
@override @override
State<TrailingWidget> createState() => _TrailingWidgetState(); State<TrailingWidget> createState() => _TrailingWidgetState();
} }
@@ -101,11 +101,11 @@ class ExpansionTrailingIcon extends StatelessWidget {
final IconData? trailingIcon; final IconData? trailingIcon;
final Color? trailingIconColor; final Color? trailingIconColor;
const ExpansionTrailingIcon({ const ExpansionTrailingIcon({
super.key, Key? key,
required this.isExpanded, required this.isExpanded,
this.trailingIcon, this.trailingIcon,
this.trailingIconColor, this.trailingIconColor,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -138,12 +138,12 @@ class LeadingWidget extends StatelessWidget {
// leadIconSize deafult value is 20. // leadIconSize deafult value is 20.
final double leadingIconSize; final double leadingIconSize;
const LeadingWidget({ const LeadingWidget({
super.key, Key? key,
required this.leadingIconSize, required this.leadingIconSize,
this.leadingIcon, this.leadingIcon,
this.leadingIconColor, this.leadingIconColor,
this.leadingIconWidget, this.leadingIconWidget,
}); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@@ -86,8 +86,8 @@ class MenuItemWidget extends StatefulWidget {
this.showOnlyLoadingState = false, this.showOnlyLoadingState = false,
this.surfaceExecutionStates = true, this.surfaceExecutionStates = true,
this.alwaysShowSuccessState = false, this.alwaysShowSuccessState = false,
super.key, Key? key,
}); }) : super(key: key);
@override @override
State<MenuItemWidget> createState() => _MenuItemWidgetState(); State<MenuItemWidget> createState() => _MenuItemWidgetState();

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