Compare commits
94 Commits
v2.0.19
...
v2.0.34-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bd95e3072 | ||
|
|
1d13814988 | ||
|
|
e54ce2f537 | ||
|
|
3c6f42c2dd | ||
|
|
a710c384bb | ||
|
|
b1743819e8 | ||
|
|
4935695a6f | ||
|
|
110ddac0c5 | ||
|
|
545bdc537e | ||
|
|
52a12915b2 | ||
|
|
0ba29c05c9 | ||
|
|
788e58e417 | ||
|
|
4229a405fd | ||
|
|
15f26240b7 | ||
|
|
c9022be745 | ||
|
|
aed86a5173 | ||
|
|
6ac48c7478 | ||
|
|
298014ee4e | ||
|
|
d8d7f7f79d | ||
|
|
795ffe20a3 | ||
|
|
f44a0eca1d | ||
|
|
d0d8bce72d | ||
|
|
004d589854 | ||
|
|
9ea551e693 | ||
|
|
1a22dfab17 | ||
|
|
a56a08630b | ||
|
|
0cb02533c7 | ||
|
|
a07e1a39e6 | ||
|
|
2bc41d529b | ||
|
|
5520ebf0ed | ||
|
|
e33c0d16f9 | ||
|
|
63ae524352 | ||
|
|
b5328d84ab | ||
|
|
529331f82d | ||
|
|
d3e8463cb7 | ||
|
|
9c4c2d2b52 | ||
|
|
1fcc66558e | ||
|
|
5279a134e0 | ||
|
|
f6f8b1ca3d | ||
|
|
27c6d8ffde | ||
|
|
310d32def2 | ||
|
|
cb1376f338 | ||
|
|
a1fcc82cb5 | ||
|
|
5d0f919027 | ||
|
|
8766face0d | ||
|
|
1f42338c2a | ||
|
|
30f5358ac0 | ||
|
|
cb0b965fb7 | ||
|
|
c9cea27aaa | ||
|
|
87d23a5327 | ||
|
|
8def85a2ad | ||
|
|
f4d8cd9e48 | ||
|
|
b785bfde57 | ||
|
|
eb4d43307e | ||
|
|
66bd489904 | ||
|
|
1b7b6e457b | ||
|
|
61c53e9d10 | ||
|
|
13fe460069 | ||
|
|
6b4e4b6822 | ||
|
|
8b9592c06e | ||
|
|
4a0301fe46 | ||
|
|
14820ad7a0 | ||
|
|
974c34a569 | ||
|
|
4dc31c3e60 | ||
|
|
fa60a22443 | ||
|
|
cf55c48b4c | ||
|
|
56290b1df6 | ||
|
|
8d468dbc91 | ||
|
|
1a654fe748 | ||
|
|
1606b74b40 | ||
|
|
8d82851741 | ||
|
|
a6ca5697d9 | ||
|
|
e01ba961a6 | ||
|
|
a256bc9922 | ||
|
|
77632b3241 | ||
|
|
d02e02a326 | ||
|
|
7fe314a16a | ||
|
|
504fa939d2 | ||
|
|
17f5a7996a | ||
|
|
cf6b4f5423 | ||
|
|
ca46765760 | ||
|
|
4de3287f55 | ||
|
|
cdc27e061b | ||
|
|
78b3d239eb | ||
|
|
005d3b0eca | ||
|
|
23ba69c6a6 | ||
|
|
d644ee97e1 | ||
|
|
0afd0d63b3 | ||
|
|
f04d9b94d9 | ||
|
|
8cd4ec30af | ||
|
|
4215810cf8 | ||
|
|
1f0c2d2aa6 | ||
|
|
dc7782fb0c | ||
|
|
c7ba0c5f33 |
141
.github/workflows/ci.yml
vendored
@@ -2,85 +2,88 @@ name: release
|
||||
|
||||
# This workflow is triggered on pushes to the repository.
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
workflow_dispatch:
|
||||
# Enable manual run
|
||||
push:
|
||||
# Sequence of patterns matched against refs/tags
|
||||
tags:
|
||||
- "v*" # Push events to matching v*, i.e. v4.2.0
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Setup Java environment in order to build the Android app.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: "11"
|
||||
build:
|
||||
# This job will run on ubuntu virtual machine
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Setup Java environment in order to build the Android app.
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-java@v2
|
||||
with:
|
||||
distribution: "adopt"
|
||||
java-version: "11"
|
||||
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: "3.13.4"
|
||||
# Setup the flutter environment.
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
cache: true
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
# Get flutter dependencies.
|
||||
- run: flutter pub get
|
||||
# Get flutter dependencies.
|
||||
- run: flutter pub get
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_auth_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_auth_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY }}
|
||||
|
||||
# Build independent apk.
|
||||
- name: Build
|
||||
run: flutter build apk --release --flavor independent --dart-define=app.flavor=independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-auth.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
# Build independent apk.
|
||||
- name: Build
|
||||
run: flutter build apk --release --flavor independent --dart-define=app.flavor=independent && mv build/app/outputs/flutter-apk/app-independent-release.apk build/app/outputs/flutter-apk/ente-auth.apk
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
# Build Play store aab.
|
||||
- name: Build
|
||||
run: flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
# Build Play store aab.
|
||||
- name: Build
|
||||
run: flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente-auth.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
- name: Checksum
|
||||
run: sha256sum build/app/outputs/flutter-apk/ente-auth.apk > build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Upload generated apk to the artifacts.
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-apk
|
||||
path: build/app/outputs/flutter-apk/ente-auth.apk
|
||||
# Upload generated apk to the artifacts.
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-apk
|
||||
path: build/app/outputs/flutter-apk/ente-auth.apk
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-checksum
|
||||
path: build/app/outputs/flutter-apk/sha256sum
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-checksum
|
||||
path: build/app/outputs/flutter-apk/sha256sum
|
||||
|
||||
# Create a Github release
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/ente-auth.apk,build/app/outputs/flutter-apk/sha256sum"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Create a Github release
|
||||
- uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "build/app/outputs/flutter-apk/ente-auth.apk,build/app/outputs/flutter-apk/sha256sum"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Upload to Play store
|
||||
- uses: ente-io/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
# Upload to Play store
|
||||
- uses: ente-io/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.auth
|
||||
releaseFiles: build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
199
.github/workflows/desktop.yml
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
name: desktop build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.16.9"
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
name: Linux
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm libsqlite3-dev locate
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
wget -O appimagetool "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
chmod +x appimagetool
|
||||
mv appimagetool /usr/local/bin/
|
||||
|
||||
- name: Build Flutter app
|
||||
env:
|
||||
LIBSODIUM_USE_PKGCONFIG: 1
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
flutter_distributor package --platform=linux --targets=deb --skip-clean
|
||||
flutter_distributor package --platform=linux --targets=rpm --skip-clean
|
||||
flutter_distributor package --platform=linux --targets=appimage --skip-clean
|
||||
mv dist/**/*-*-linux.deb auth-linux-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}-x86_64.deb
|
||||
mv dist/**/*-*-linux.rpm auth-linux-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}-x86_64.rpm
|
||||
mv dist/**/*-*-linux.AppImage auth-linux-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}-x86_64.AppImage
|
||||
|
||||
- uses: svenstaro/upload-release-action@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: auth-linux-*
|
||||
file_glob: true
|
||||
prerelease: ${{ github.event_name != 'release' }}
|
||||
release_name: ${{ github.event_name == 'release' && github.event.release.name || 'Nightly Build' }}
|
||||
tag: ${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}
|
||||
overwrite: true
|
||||
|
||||
build-macos:
|
||||
name: Macos
|
||||
runs-on: macos-13
|
||||
env:
|
||||
APP_NAME: auth-macos-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}-universal
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip3 install codemagic-cli-tools
|
||||
|
||||
- name: Set up provisioning profiles from environment variables
|
||||
env:
|
||||
CM_PROVISIONING_PROFILE: ${{ secrets.MAC_OS_BUILD_PROVISION_PROFILE_BASE64 }}
|
||||
run: |
|
||||
PROFILES_HOME="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||
mkdir -p "$PROFILES_HOME"
|
||||
PROFILE_PATH="$(mktemp "$PROFILES_HOME"/$(uuidgen).provisionprofile)"
|
||||
echo ${CM_PROVISIONING_PROFILE} | base64 --decode > "$PROFILE_PATH"
|
||||
echo "Saved provisioning profile $PROFILE_PATH"
|
||||
|
||||
- name: Get certificates
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_OS_CERTIFICATE }}
|
||||
P12_PASSWORD: ${{ secrets.MAC_OS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
# create variables
|
||||
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
|
||||
|
||||
# copy certificates from base64
|
||||
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
|
||||
|
||||
# add certificate to keychain
|
||||
keychain initialize
|
||||
keychain add-certificates --certificate $CERTIFICATE_PATH --certificate-password $P12_PASSWORD
|
||||
|
||||
# Use profile in current project
|
||||
xcode-project use-profiles --project=macos/**/*.xcodeproj
|
||||
|
||||
- name: DMG build dependencies
|
||||
run: |
|
||||
python3 -m pip install setuptools
|
||||
npm install -g appdmg
|
||||
|
||||
- name: Build Macos App
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
flutter_distributor package --platform=macos --targets=dmg --skip-clean
|
||||
mv dist/**/ente_auth-*-macos.dmg $APP_NAME.dmg
|
||||
|
||||
- name: Signing DMG
|
||||
run: |
|
||||
CERT_NAME=$(security find-identity -v -p codesigning | grep "Developer ID Application" | awk -F'"' '{print $2}' | grep -m1 "")
|
||||
codesign --force --timestamp --sign "$CERT_NAME" --options runtime $APP_NAME.dmg
|
||||
codesign --verify --verbose=4 $APP_NAME.dmg
|
||||
|
||||
- name: Notarize and Staple DMG
|
||||
env:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
xcrun notarytool submit $APP_NAME.dmg \
|
||||
--wait \
|
||||
--apple-id $APPLE_ID \
|
||||
--password $APPLE_PASSWORD \
|
||||
--team-id $APPLE_TEAM_ID
|
||||
xcrun stapler staple $APP_NAME.dmg
|
||||
|
||||
- uses: svenstaro/upload-release-action@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: auth-macos-*.dmg
|
||||
file_glob: true
|
||||
prerelease: ${{ github.event_name != 'release' }}
|
||||
release_name: ${{ github.event_name == 'release' && github.event.release.name || 'Nightly Build' }}
|
||||
tag: ${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}
|
||||
overwrite: true
|
||||
|
||||
build_windows:
|
||||
name: Windows
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
APP_NAME: auth-windows-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Setup flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
cache-key: "flutter-:os:-:channel:-:version:-:arch:"
|
||||
channel: stable
|
||||
|
||||
# Fetch sub modules
|
||||
- run: git submodule update --init --recursive
|
||||
|
||||
- name: Build Flutter app
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
dart pub global activate flutter_distributor
|
||||
make innoinstall
|
||||
flutter_distributor package --platform=windows --targets=exe --skip-clean
|
||||
cp dist/**/ente_auth-*-windows-setup.exe ${{ env.APP_NAME }}.exe
|
||||
|
||||
- name: Copy Windows release files
|
||||
run: cp -r build/windows/x64/runner/Release auth-windows-exe
|
||||
|
||||
- name: Code Sign Windows
|
||||
uses: dlemstra/code-sign-action@main
|
||||
with:
|
||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||
password: "${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}"
|
||||
files: |
|
||||
${{ env.APP_NAME }}.exe
|
||||
auth-windows-exe/auth.exe
|
||||
|
||||
- name: Prepare Zip Windows
|
||||
run: tar.exe -a -c -f auth-windows-${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}.zip auth-windows-exe
|
||||
|
||||
- uses: svenstaro/upload-release-action@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: auth-windows-*
|
||||
file_glob: true
|
||||
prerelease: ${{ github.event_name != 'release' }}
|
||||
release_name: ${{ github.event_name == 'release' && github.event.release.name || 'Nightly Build' }}
|
||||
tag: ${{ github.event_name == 'release' && github.event.release.tag_name || 'nightly' }}
|
||||
overwrite: true
|
||||
1
.gitignore
vendored
@@ -37,3 +37,4 @@ lib/generated_plugin_registrant.dart
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
|
||||
android/key.properties
|
||||
dist/
|
||||
26
.vscode/launch.json
vendored
@@ -7,7 +7,10 @@
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"program": "lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://localhost:8080"]
|
||||
"args": [
|
||||
"--dart-define",
|
||||
"endpoint=http://localhost:8080"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Android Dev",
|
||||
@@ -26,21 +29,32 @@
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://192.168.1.30:8080"]
|
||||
"args": [
|
||||
"--dart-define",
|
||||
"endpoint=http://192.168.1.30:8080"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "iOS Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart"]
|
||||
"args": [
|
||||
"--target",
|
||||
"lib/main.dart"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Android Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart", "--flavor", "independent"]
|
||||
}
|
||||
"args": [
|
||||
"--target",
|
||||
"lib/main.dart",
|
||||
"--flavor",
|
||||
"independent"
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
3
.vscode/settings.json
vendored
@@ -3,5 +3,6 @@
|
||||
"activityBar.background": "#44116A",
|
||||
"titleBar.activeBackground": "#5F1895",
|
||||
"titleBar.activeForeground": "#FDFBFE"
|
||||
}
|
||||
},
|
||||
"dart.flutterSdkPath": "flutter/bin",
|
||||
}
|
||||
@@ -1,36 +1,49 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for showing interest in contributing to ente Authenticator. There are a couple of ways to help
|
||||
out. This document contains some general guidelines for each type of
|
||||
contribution.
|
||||
Thank you for showing interest in contributing to ente Authenticator. There are
|
||||
a couple of ways to help out. This document contains some general guidelines for
|
||||
each type of contribution.
|
||||
|
||||
|
||||
## Translations
|
||||
[](https://crowdin.com/project/ente-authenticator-app)
|
||||
|
||||
We use [Crowdin](https://crowdin.com/project/ente-authenticator-app) to crowdsource
|
||||
translations of ente Authenticator.
|
||||
If your language is not listed for translation, feel free to [create a GitHub issue](https://github.com/ente-io/auth/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A) to have it added.
|
||||
We use [Crowdin](https://crowdin.com/project/ente-authenticator-app) to
|
||||
crowdsource translations of ente Authenticator. If your language is not listed
|
||||
for translation, feel free to [create a GitHub
|
||||
issue](https://github.com/ente-io/auth/issues/new?title=Request+for+New+Language+Translation&body=Language+name%3A)
|
||||
to have it added.
|
||||
|
||||
## Icons
|
||||
|
||||
ente Auth supports the icon pack provided by
|
||||
[simple-icons](https://github.com/simple-icons/simple-icons).
|
||||
|
||||
If you would like to add your own custom icon, please open a pull-request
|
||||
with the relevant SVG and color
|
||||
code ([example PR](https://github.com/ente-io/auth/pull/213/files)).
|
||||
If you would like to add your own custom icon, please open a pull-request with
|
||||
the relevant SVG placed within `assets/custom-icons/icons` and add the
|
||||
corresponding entry within `assets/custom-icons/_data/custom-icons.json`.
|
||||
|
||||
This JSON file contains the following attributes:
|
||||
|
||||
| Attribute | Usecase | Required |
|
||||
|---|---|---|
|
||||
| `title` | Name of the service. | Yes |
|
||||
| `slug` | If the icon's SVG file has a name different from the `title` | No |
|
||||
| `hex` | Color code for the icon | No |
|
||||
| `altNames` | If the same service goes by different names or has different instances (eg. Mastodon) | No |
|
||||
|
||||
Here is an [example PR](https://github.com/ente-io/auth/pull/213/files).
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
If you're planning on adding a new feature or making other changes, please
|
||||
discuss it with us by creating [an
|
||||
issue](https://github.com/ente-io/auth/issues/new)
|
||||
on GitHub. Discussing your idea with us first ensures that everyone is on the
|
||||
same page before you start working on your change.
|
||||
issue](https://github.com/ente-io/auth/issues/new) on GitHub. Discussing your
|
||||
idea with us first ensures that everyone is on the same page before you start
|
||||
working on your change.
|
||||
|
||||
### 💻 Setup
|
||||
### Setup
|
||||
|
||||
1. [Install Flutter v3.10.6](https://flutter.dev/docs/get-started/install)
|
||||
2. Clone this repository with `git clone git@github.com:ente-io/auth.git`
|
||||
@@ -43,9 +56,12 @@ same page before you start working on your change.
|
||||
|
||||
|
||||
#### Localization
|
||||
If the feature requires adding new strings, you can do that by following these steps:
|
||||
If the feature requires adding new strings, you can do that by following these
|
||||
steps:
|
||||
|
||||
1. Add a new entry inside [app_en.arb](https://github.com/ente-io/auth/blob/main/lib/l10n/arb/app_en.arb) (Remember to save)
|
||||
1. Add a new entry inside
|
||||
[app_en.arb](https://github.com/ente-io/auth/blob/main/lib/l10n/arb/app_en.arb)
|
||||
(Remember to save)
|
||||
2. In your dart file, add follwing import
|
||||
```dart
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
|
||||
@@ -41,8 +41,8 @@ analyzer:
|
||||
use_rethrow_when_possible: info
|
||||
require_trailing_commas: error
|
||||
|
||||
prefer_const_constructors: error # too many warnings
|
||||
prefer_const_declarations: error # too many warnings
|
||||
prefer_const_constructors: warning
|
||||
prefer_const_declarations: warning
|
||||
prefer_const_constructors_in_immutables: ignore # too many warnings
|
||||
|
||||
avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides
|
||||
|
||||
@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
compileSdkVersion 34
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
@@ -46,7 +46,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId "io.ente.auth"
|
||||
minSdkVersion 20
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
@@ -56,7 +56,7 @@ android {
|
||||
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH"))
|
||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
|
||||
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
|
||||
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
|
||||
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
|
||||
@@ -109,6 +109,7 @@ dependencies {
|
||||
implementation 'io.sentry:sentry-android:2.0.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
implementation 'com.google.guava:guava:28.2-android'
|
||||
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"title": "Addy.io",
|
||||
"slug": "addy_io"
|
||||
},
|
||||
{
|
||||
"title": "Airtable"
|
||||
},
|
||||
{
|
||||
"title": "Anycoin Direct",
|
||||
"slug": "anycoindirect"
|
||||
@@ -27,6 +30,16 @@
|
||||
{
|
||||
"title": "Bitwarden"
|
||||
},
|
||||
{
|
||||
"title": "BorgBase",
|
||||
"altNames": ["borg"],
|
||||
"slug": "BorgBase",
|
||||
"hex": "222C31"
|
||||
},
|
||||
{
|
||||
"title": "Brave Creators",
|
||||
"slug": "brave_creators"
|
||||
},
|
||||
{
|
||||
"title": "Bybit"
|
||||
},
|
||||
@@ -43,10 +56,34 @@
|
||||
"slug": "controld",
|
||||
"hex": "5FD800"
|
||||
},
|
||||
{
|
||||
"title": "Crowdpear"
|
||||
},
|
||||
{
|
||||
"title": "DEGIRO"
|
||||
},
|
||||
{
|
||||
"title": "Discourse"
|
||||
},
|
||||
{
|
||||
"title": "dus.net",
|
||||
"slug": "dusnet"
|
||||
},
|
||||
{
|
||||
"title": "ente",
|
||||
"hex": "1DB954"
|
||||
},
|
||||
{
|
||||
"title": "Epic Games",
|
||||
"slug": "epic_games",
|
||||
"hex": "000000"
|
||||
},
|
||||
{
|
||||
"title": "Esketit"
|
||||
},
|
||||
{
|
||||
"title": "Estateguru"
|
||||
},
|
||||
{
|
||||
"title": "Filen",
|
||||
"hex": "858585"
|
||||
@@ -59,19 +96,44 @@
|
||||
"title": "GitHub",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "GitLab"
|
||||
},
|
||||
{
|
||||
"title": "Google"
|
||||
},
|
||||
{
|
||||
"title": "Gosuslugi",
|
||||
"altNames": ["Госуслуги"],
|
||||
"slug": "Gosuslugi",
|
||||
"hex": "EE2F53"
|
||||
},
|
||||
{
|
||||
"title": "ING"
|
||||
},
|
||||
{
|
||||
"title": "INWX"
|
||||
},
|
||||
{
|
||||
"title": "Instagram"
|
||||
},
|
||||
{
|
||||
"title": "IVPN",
|
||||
"slug": "IVPN",
|
||||
"hex": "FA3243"
|
||||
},
|
||||
{
|
||||
"title": "IceDrive",
|
||||
"slug": "Icedrive",
|
||||
"hex": "1F4FD0"
|
||||
},
|
||||
{
|
||||
"title": "Jagex",
|
||||
"hex": "D3D800"
|
||||
},
|
||||
{
|
||||
"title": "Kagi"
|
||||
},
|
||||
{
|
||||
"title": "KPN",
|
||||
"color": "00CC00"
|
||||
@@ -80,6 +142,13 @@
|
||||
"title": "Kick",
|
||||
"hex": "53FC19"
|
||||
},
|
||||
{
|
||||
"title": "Kite"
|
||||
},
|
||||
{
|
||||
"title": "Koofr",
|
||||
"hex": "71BA05"
|
||||
},
|
||||
{
|
||||
"title": "Kraken",
|
||||
"hex": "5848D5"
|
||||
@@ -91,9 +160,9 @@
|
||||
"title": "KuCoin",
|
||||
"hex": "01BC8D"
|
||||
},
|
||||
{
|
||||
{
|
||||
"title": "La Poste",
|
||||
"slug": "laposte"
|
||||
"slug": "laposte"
|
||||
},
|
||||
{
|
||||
"title": "Mastodon",
|
||||
@@ -101,12 +170,24 @@
|
||||
"slug": "mastodon",
|
||||
"hex": "6364FF"
|
||||
},
|
||||
{
|
||||
"title": "Murena",
|
||||
"altNames": ["eCloud"],
|
||||
"slug": "ecloud",
|
||||
"hex": "EC6A55"
|
||||
},
|
||||
{
|
||||
"title": "Microsoft"
|
||||
},
|
||||
{
|
||||
"title": "Mintos"
|
||||
},
|
||||
{
|
||||
"title": "Mozilla"
|
||||
},
|
||||
{
|
||||
"title": "NextDNS"
|
||||
},
|
||||
{
|
||||
"title": "ngrok",
|
||||
"hex": "858585"
|
||||
@@ -114,6 +195,9 @@
|
||||
{
|
||||
"title": "Njalla"
|
||||
},
|
||||
{
|
||||
"title": "Notesnook"
|
||||
},
|
||||
{
|
||||
"title": "Notion"
|
||||
},
|
||||
@@ -129,10 +213,19 @@
|
||||
{
|
||||
"title": "PayPal"
|
||||
},
|
||||
{
|
||||
"title": "pCloud",
|
||||
"slug": "pCloud",
|
||||
"hex": "1EBCC5"
|
||||
},
|
||||
{
|
||||
"title": "Peerberry",
|
||||
"hex": "03E5A5"
|
||||
},
|
||||
{
|
||||
"title": "Pingvin Share",
|
||||
"hex": "485099"
|
||||
},
|
||||
{
|
||||
"title": "Plutus",
|
||||
"hex": "DEC685"
|
||||
@@ -159,13 +252,31 @@
|
||||
{
|
||||
"title": "Proton"
|
||||
},
|
||||
{
|
||||
"title": "Proxmox"
|
||||
},
|
||||
{
|
||||
"title": "Revolt",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Rust Language Forum",
|
||||
"slug": "rust_language_forum",
|
||||
"hex": "000000"
|
||||
},
|
||||
{
|
||||
"title": "service-bw"
|
||||
},
|
||||
{
|
||||
"title": "SimpleLogin"
|
||||
},
|
||||
{
|
||||
"title": "Sipgate"
|
||||
},
|
||||
{
|
||||
"title": "Skiff",
|
||||
"hex": "EF5A3C"
|
||||
},
|
||||
{
|
||||
"title": "Snapchat"
|
||||
},
|
||||
@@ -174,6 +285,11 @@
|
||||
"slug": "standardnotes",
|
||||
"hex": "2173E6"
|
||||
},
|
||||
{
|
||||
"title": "TCPShield",
|
||||
"slug": "tcpshield",
|
||||
"hex": "FFFFFF"
|
||||
},
|
||||
{
|
||||
"title": "Techlore"
|
||||
},
|
||||
@@ -202,10 +318,6 @@
|
||||
"title": "Twingate",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Twitter",
|
||||
"slug": "x"
|
||||
},
|
||||
{
|
||||
"title": "Ubisoft",
|
||||
"hex": "4285f4"
|
||||
@@ -214,6 +326,11 @@
|
||||
"title": "Unity",
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Uphold",
|
||||
"slug": "uphold",
|
||||
"hex": "6FE68A"
|
||||
},
|
||||
{
|
||||
"title": "WHMCS"
|
||||
},
|
||||
@@ -225,17 +342,15 @@
|
||||
"title": "Wise"
|
||||
},
|
||||
{
|
||||
"title": "X"
|
||||
"title": "X",
|
||||
"altNames": ["twitter"],
|
||||
"slug": "x"
|
||||
},
|
||||
{
|
||||
"title": "NextDNS"
|
||||
},
|
||||
{
|
||||
"title": "Skiff",
|
||||
"hex": "EF5A3C"
|
||||
},
|
||||
{
|
||||
"title": "zzz_dev_test_icon"
|
||||
"title": "Yandex",
|
||||
"altNames": ["Ya", "Яндекс"],
|
||||
"slug": "Yandex",
|
||||
"hex": "FC3F1D"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
4
assets/custom-icons/icons/BorgBase.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#263238"/>
|
||||
<path d="M768 554.7V640c0 23.6-19.1 42.7-42.7 42.7H298.7c-23.6 0-42.7-19.1-42.7-42.7v-85.3c0-23.6 19.1-42.7 42.7-42.7h426.7c23.5 0 42.6 19.1 42.6 42.7zm-42.7-71.1c9.4 0 18.7 1.9 27.4 5.5l-85.8-128.7c-7.9-11.9-21.2-19-35.5-19H392.6c-14.3 0-27.6 7.1-35.5 19L271.3 489c8.7-3.6 18-5.5 27.4-5.5h426.6zm-42.6 85.3c-15.7 0-28.4 12.7-28.4 28.4s12.7 28.4 28.4 28.4 28.4-12.7 28.4-28.4-12.7-28.4-28.4-28.4zm-85.4 0c-15.7 0-28.4 12.7-28.4 28.4s12.7 28.4 28.4 28.4 28.4-12.7 28.4-28.4c.1-15.7-12.7-28.4-28.4-28.4z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 662 B |
4
assets/custom-icons/icons/Gosuslugi.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#ee2f53"/>
|
||||
<path d="M768 511.51a892.66 892.66 0 0 1-5.18 94.8 138.36 138.36 0 0 1-47.4 84.35S680.6 716 637.64 742.17a869.63 869.63 0 0 1-82.22 43.29 116.91 116.91 0 0 1-47.4 9 119.39 119.39 0 0 1-47.4-8.73 864.24 864.24 0 0 1-82.22-43.29c-43-25.38-77.77-51.51-77.77-51.51-9.63-7.79-19.23-19.17-27.29-31.93a2.19 2.19 0 0 1 1.87-3.35h36.24a3 3 0 0 1 2.26 1 93.83 93.83 0 0 0 11.79 11.44s30.64 22.8 68.44 45a762.9 762.9 0 0 0 72.35 37.78 105.85 105.85 0 0 0 41.71 7.61 103.61 103.61 0 0 0 41.72-7.82 767.21 767.21 0 0 0 72.35-37.78c37.81-22.8 68.44-45 68.44-45a120.5 120.5 0 0 0 41.72-73.62 772.63 772.63 0 0 0 4.56-82.74c-.65-44.76-5.21-82.54-5.21-82.54-2-25.41-21.51-58-41.06-73.61a747 747 0 0 0-68.44-44.3 762.58 762.58 0 0 0-72.35-37.78 105.83 105.83 0 0 0-41.73-7.78 103.61 103.61 0 0 0-41.72 7.82 767.4 767.4 0 0 0-72.35 37.78c-37.81 22.8-68.44 44.3-68.44 44.3a120.46 120.46 0 0 0-13.64 13.39 3 3 0 0 1-2.27 1h-36.1a2.18 2.18 0 0 1-1.89-3.3 137.79 137.79 0 0 1 29-34s34.81-24.63 77.77-50.76a869.5 869.5 0 0 1 82.22-43.29 116.89 116.89 0 0 1 47.4-9 119.4 119.4 0 0 1 47.4 9 864.24 864.24 0 0 1 82.22 43.29 848.16 848.16 0 0 1 77.77 50.76c22.22 17.92 44.44 55.24 46.66 84.35.04.09 5.23 43.38 5.97 94.66Zm-318 76.58h17.1a.89.89 0 0 0 .89-.89v-69.48a.89.89 0 0 0-.89-.89H415a.88.88 0 0 0-.89.85 296 296 0 0 1-3.78 35.51 289.23 289.23 0 0 1-8.1 33.77.89.89 0 0 0 .85 1.14h17.52a.88.88 0 0 0 .83-.6 250.76 250.76 0 0 0 7.12-28.54 228.6 228.6 0 0 0 3.45-25.43h17.13v53.67a.89.89 0 0 0 .87.89ZM256.89 484.17h17a.89.89 0 0 0 .89-.89v-53.67h26a.87.87 0 0 0 .9-.54l6.22-14.93a.89.89 0 0 0-.81-1.23h-50.2a.89.89 0 0 0-.89.89v69.49a.89.89 0 0 0 .89.88Zm322.9 103.92a.89.89 0 0 0 .89-.89v-53.67h26a.87.87 0 0 0 .9-.54l6.23-14.93a.89.89 0 0 0-.81-1.23h-50.3a.89.89 0 0 0-.89.89v69.48a.89.89 0 0 0 .89.89Zm-290.16 25.63C303.27 586 313.31 561.34 327 518a.89.89 0 0 0-.84-1.17h-17.09a.9.9 0 0 0-.86.65 455.34 455.34 0 0 1-15.81 47L275 517.4a.89.89 0 0 0-.83-.58h-17.28a.88.88 0 0 0-.83 1.18L282 588.1c-3.66 7.69-7.72 15.79-12.37 24.81a.89.89 0 0 0 .79 1.3h18.44a.9.9 0 0 0 .77-.49Zm204.2.49h18.44a.9.9 0 0 0 .79-.49C526.7 586 536.75 561.34 550.47 518a.9.9 0 0 0-.84-1.17H532.5a.9.9 0 0 0-.86.65 455.2 455.2 0 0 1-15.81 47l-17.4-47.08a.89.89 0 0 0-.83-.58h-17.28a.88.88 0 0 0-.83 1.18l25.92 70.09c-3.66 7.69-7.72 15.79-12.37 24.81a.89.89 0 0 0 .79 1.31Zm-124-82.78a46.93 46.93 0 0 1 13.7 2.14.92.92 0 0 0 1.08-.46c1.94-3.63 4.29-8.08 6.93-13.52a.91.91 0 0 0 0-.77 1 1 0 0 0-.58-.53 76.92 76.92 0 0 0-22.36-3.44c-24.49 0-35.41 11.5-35.41 37.31 0 26.18 10.92 37.86 35.41 37.86 6.14 0 18.23-1.28 24.07-3.6a.94.94 0 0 0 .53-1.18l-5.06-14a1 1 0 0 0-.49-.53 1 1 0 0 0-.72 0 59.43 59.43 0 0 1-17.1 2.74c-11.69 0-17.26-3.14-17.26-21-.01-14.29 1.7-21.01 17.25-21.01Zm58.65-103.92a46.94 46.94 0 0 1 13.7 2.14.92.92 0 0 0 1.08-.46c1.94-3.63 4.29-8.08 6.93-13.52a.91.91 0 0 0 0-.77 1 1 0 0 0-.58-.53 76.93 76.93 0 0 0-22.36-3.44c-24.49 0-35.41 11.5-35.41 37.31 0 26.18 10.92 37.86 35.41 37.86 6.14 0 18.23-1.28 24.07-3.6a.94.94 0 0 0 .53-1.18l-5.06-14a1 1 0 0 0-.49-.53 1 1 0 0 0-.72 0 59.41 59.41 0 0 1-17.1 2.74c-11.69 0-17.26-3.14-17.26-21-.01-14.29 1.7-21.01 17.25-21.01Zm-49.24 20.73c0 27.26-9.41 37.86-33.55 37.86-23.95 0-33.27-10.6-33.27-37.86 0-26.85 9.32-37.31 33.27-37.31 24.14.01 33.55 10.47 33.55 37.32Zm-19 .27c0-20.16-3.78-21.95-14.59-21.95-10.6 0-14.31 1.79-14.31 21.95 0 18.93 3.4 22 14.31 22 11.16-.04 14.63-3.06 14.63-21.99Zm279.66 68.32h-17.05a.88.88 0 0 0-.89.89v40.58c.41 22.82 8.69 31.76 29.13 31.76a102.78 102.78 0 0 0 33.16-6 .9.9 0 0 0 .6-.87v-65.48a.85.85 0 0 0-.87-.89H667a.88.88 0 0 0-.89.89v53.69a42 42 0 0 1-11.54 1.53c-10.34 0-13.34-2.91-13.73-15v-40.22a.88.88 0 0 0-.94-.88Z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
4
assets/custom-icons/icons/IVPN.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#f34"/>
|
||||
<path d="M267 451.92c-.52-22.65-28-28.12-31.37-29-3.62-.92-3.24-4.88-3.24-4.88h85.08a1.69 1.69 0 0 1 1.3.61 1.73 1.73 0 0 1 .38 1.4l-29.62 181.87a1.71 1.71 0 0 1-1.58 1.45l-44.17 2.56h-.1a1.69 1.69 0 0 1-1.27-.57 1.75 1.75 0 0 1-.41-1.44Zm225.52-33a1.73 1.73 0 0 1 .05 1.67l-87.62 171.6a1.66 1.66 0 0 1-1.39.91l-50.37 2.9h-.09a1.68 1.68 0 0 1-1.64-1.39l-32.31-174.52a1.73 1.73 0 0 1 .36-1.4 1.65 1.65 0 0 1 1.28-.62h43.63a1.68 1.68 0 0 1 1.64 1.4l20.36 116.08L446.23 419a1.67 1.67 0 0 1 1.48-.91H491a1.67 1.67 0 0 1 1.47.8Zm135 36.84a69.67 69.67 0 0 1-.83 18.19L622.81 496c-2.39 13.65-6.86 24.4-13.27 31.95s-13.05 13-19.92 16.46a66.4 66.4 0 0 1-25.86 6.87h-40.51l-5 31a1.65 1.65 0 0 1-1.51 1.4l-41.62 2.44h-.09a1.6 1.6 0 0 1-1.21-.56 1.71 1.71 0 0 1-.39-1.39l26.73-164.74a1.64 1.64 0 0 1 1.61-1.4h82.79c9.35.58 17.54 2.9 24.42 6.92a43.41 43.41 0 0 1 8.22 6.69 35.3 35.3 0 0 1 6.7 10.11 45.78 45.78 0 0 1 3.55 13.97Zm-48 39.37 3.48-21.49a16.62 16.62 0 0 0-.74-6.41 11.77 11.77 0 0 0-3.08-4.35c-1.31-1.2-3.51-1.81-6.52-1.81h-35.29L530 508h34.73a13.15 13.15 0 0 0 7.39-1.82 16.82 16.82 0 0 0 4.58-4.44 17 17 0 0 0 2.75-6.65Zm210.57-77a1.61 1.61 0 0 1 1.26.62 1.83 1.83 0 0 1 .37 1.43l-24.25 156.17a1.69 1.69 0 0 1-1.53 1.47l-57.1 3.49a1.67 1.67 0 0 1-1.65-1.17l-22.69-67.51-10.74 69.44a1.69 1.69 0 0 1-1.53 1.47l-42.85 2.61a1.6 1.6 0 0 1-1.23-.59 1.84 1.84 0 0 1-.4-1.47l25.7-164.56a1.69 1.69 0 0 1 1.63-1.47h42.11a1.66 1.66 0 0 1 1.55 1.16l32.53 96.39 15-96.08a1.69 1.69 0 0 1 1.63-1.47Z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
4
assets/custom-icons/icons/Icedrive.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#1f4fd0"/>
|
||||
<path d="M476.88 258.41C532 250.77 589.62 261.39 638 289c54.28 30.53 97 81.45 116.52 140.69 16.71 48.8 17.87 102.62 4 152.26a256.48 256.48 0 0 1-96 137.42c-42.36 31-94.6 48.48-147.1 48.59a252.5 252.5 0 0 1-157.68-51.19c-50.49-37.72-86-94.87-97.39-156.9-11.4-60.07-.78-124.17 30.4-176.86 38.62-67.18 109.25-114.78 186.13-124.6m-76.49 135.95c-20.82-11.19-48.29 2.33-53.85 24.87-14.89 9.82-29.46 20.5-41.72 33.51-14.86 49.8-10.3 105 12.21 151.83C340.53 654.71 384.18 695 436 714.51a215.88 215.88 0 0 0 121.46 9.23 96.58 96.58 0 0 1-12.15-22.89c-14.38-38.71 3.78-87 41.77-104.39 19-11.21 41.77-3.89 62.19-9.39 26-6.25 47.7-25 61.65-47.3 5.37-7.62.86-17.41-4.88-23.37-23-23-53.82-35.25-82.42-49.34-11.78-14.41-13-37.05-29.89-47.73-56.26-37-129.37-44.72-193.3-25m127.89 59.32c-6.17 3.38-13.84 11.27-7.81 18.32 15 0 30 1.74 45 .38 6-1.5 13.15-8.08 8.37-14.38-6.2-2.82-13.09-3.41-19.61-5.18-8.42-2.15-18.06-4.05-25.94.86" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
20
assets/custom-icons/icons/Notesnook.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 339 339">
|
||||
<defs />
|
||||
<defs>
|
||||
<linearGradient id="b" x1="193.9" x2="198.7" y1="166.8" y2="223.3" gradientTransform="rotate(5 4448 -4204) scale(2.93671)" gradientUnits="userSpaceOnUse" xlink:href="#a" />
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" />
|
||||
<stop offset="1" stop-color="#fff9f9" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x1="167.8" x2="270.6" y1="76.9" y2="64.2" gradientTransform="rotate(5 465 -2050) scale(1.50082)" gradientUnits="userSpaceOnUse" xlink:href="#a" />
|
||||
</defs>
|
||||
<g transform="translate(0 42)">
|
||||
<path fill="url(#b)" d="M160 205l154 42-141 44-155-42z" />
|
||||
<path fill="url(#c)" d="M160-35v240l154 42 1-253z" />
|
||||
<path fill="none" stroke-width="1.2" d="M160 205V-35m0 240L18 249m142-44l154 41" />
|
||||
<path d="M84 109l35 54V98l21-7v91l-27 9-35-54v65l-21 6v-91z" />
|
||||
<rect width="86.1" height="12.6" x="185" y="97" fill="#bebebe" ry="2.3" transform="skewY(15) scale(.9669 1)" />
|
||||
<path fill="#bebebe" d="M181 169l99 26 2 3v8c0 1-1 2-2 1l-99-26-2-3v-7c0-2 1-2 2-2zm0-47l99 27 2 2v8l-2 2-99-27c-1 0-2-1-2-3v-7l2-2z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
4
assets/custom-icons/icons/Yandex.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#fc3f1d"/>
|
||||
<path d="M390.3 542.51q0 14 1.37 28.06h-26.54a35.68 35.68 0 0 1-2.06-9.12h-1.14c-5.26 5.48-12.13 10.72-28.14 10.72-21 0-35.23-13-35.23-36.27s18.53-37 60.17-37h4.35v-6.39c0-12.32-5.95-16.88-19-16.88-14 0-30 5.93-37.29 11.41v-22.56a81.65 81.65 0 0 1 40.5-10.48c28.83 0 43 10.49 43 38.33ZM363.07 517h-3.89c-23.34 0-32.71 4.79-32.71 18.25 0 10.27 5.72 17.11 17.16 17.11 9.61 0 16-4.11 19.45-9.13Zm76.64 53.61h-27.22V455.82H439l.69 9.58h1.37c5.49-5.48 15.1-11.41 31.34-11.41 22.19 0 31.34 10 31.34 32.85v83.73h-27.2v-81c0-8.67-4.35-12.78-14-12.78-9.84 0-17.84 5.25-22.88 11.41ZM522.07 514c0-38.33 18.3-60.46 44.61-60.46 11.9 0 22 5.48 28.83 15.74V412h27.22v158.57h-25.17l-1.37-14.83c-7.32 11.18-17.84 17.11-30.65 17.11-25.62 0-43.47-21-43.47-58.86m28.14 0c0 26 7.32 37.41 22.19 37.41 15.33 0 23.11-11.63 23.11-38.33 0-26.46-7.32-38.1-22-38.1-15.56 0-23.33 11.86-23.33 39m144.35 58.86c-34.77 0-54.45-19.39-54.45-59.32 0-34.68 15.79-60 49-60 27.22 0 44.84 15.06 44.84 55v14.37h-65.67c1.14 19.16 8.69 28.52 28.6 28.52 13.27 0 27.45-5 35.92-10.72v22.13c-8 5.47-20.82 10-38.2 10m-26.31-70h38v-2.28c0-14.37-4.12-25.55-17.62-25.55-13 0-19.67 9.58-20.36 27.83m122.62 29-23.11 38.78h-27.71l36.15-59.77-34.77-55h30.65l22 34.68 19.67-34.68H841l-32.49 55.67L846 570.57h-30.65Zm-532.6 38.76v-19.39c0-26.24-4.58-38.1-15.1-60.91L207.28 412H178l42.32 91.48c8.69 18.71 11.21 29.43 11.21 51.1v16Zm18.07-81.9L310.23 412h-28.14l-33.4 76.66Z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
assets/custom-icons/icons/airtable.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Airtable</title><path fill="#ffba05" d="M11.992 1.966c-.434 0-.87.086-1.28.257L1.779 5.917c-.503.208-.49.908.012 1.116l8.982 3.558a3.266 3.266 0 0 0 2.454 0l8.982-3.558c.503-.196.503-.908.012-1.116l-8.957-3.694a3.255 3.255 0 0 0-1.272-.257z"/><path fill="#39caff" d="M23.4 8.056a.589.589 0 0 0-.222.045l-10.012 3.877a.612.612 0 0 0-.38.564v8.896a.6.6 0 0 0 .821.552L23.62 18.1a.583.583 0 0 0 .38-.551V8.653a.6.6 0 0 0-.6-.596z"/><path fill="#dc043b" d="M.676 8.095a.644.644 0 0 0-.48.19C.086 8.396 0 8.53 0 8.69v8.355c0 .442.515.737.908.54l6.27-3.006.307-.147 2.969-1.436c.466-.22.43-.908-.061-1.092L.883 8.138a.57.57 0 0 0-.207-.044z"/><path fill-opacity="0.25" d="M10.451,12.997l-2.972,1.434l-7.287,-6.144c0.046,-0.046 0.098,-0.084 0.152,-0.114c0.15,-0.09 0.363,-0.114 0.545,-0.042l9.512,3.769c0.483,0.191 0.521,0.869 0.05,1.097"/></svg>
|
||||
|
After Width: | Height: | Size: 918 B |
19
assets/custom-icons/icons/brave_creators.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg fill="none" viewBox="0 0 500 500">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="0" x2="1" y1="1.007" y2="1.007">
|
||||
<stop stop-color="#F50"/>
|
||||
<stop offset=".41" stop-color="#F50"/>
|
||||
<stop offset=".582" stop-color="#FF2000"/>
|
||||
<stop offset="1" stop-color="#FF2000"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1=".021" x2="1" y1=".996" y2=".996">
|
||||
<stop stop-color="#FF452A"/>
|
||||
<stop offset="1" stop-color="#FF2000"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g fill-rule="evenodd" clip-rule="evenodd">
|
||||
<path fill="url(#a)" d="m448.34 120.054 11.734-28.753s-14.932-15.98-33.064-34.088c-18.123-18.108-56.513-7.454-56.513-7.454L326.78.214H173.228L129.51 49.76s-38.39-10.654-56.514 7.454C54.867 75.32 39.942 91.3 39.942 91.3l11.733 28.753-14.94 42.606s43.912 166.107 49.06 186.397c10.129 39.939 17.059 55.38 45.851 75.622 28.785 20.236 81.036 55.38 89.561 60.714 8.534 5.32 19.203 14.393 28.8 14.393 9.598 0 20.26-9.074 28.786-14.393 8.533-5.326 60.776-40.478 89.576-60.714 28.785-20.235 35.723-35.683 45.852-75.622 5.14-20.29 49.044-186.397 49.044-186.397l-14.924-42.606z"/>
|
||||
<path fill="#fff" d="M304.386 90.23c6.398 0 53.854-9.05 53.854-9.05s56.232 67.894 56.232 82.411c0 11.999-4.842 16.692-10.536 22.23-1.19 1.158-2.425 2.347-3.653 3.66l-42.16 44.719a73.683 73.683 0 0 1-1.377 1.408c-4.208 4.223-10.403 10.45-6.039 20.79l.9 2.104c4.795 11.201 10.716 25.03 3.183 39.032-8.017 14.908-21.745 24.858-30.544 23.208-8.792-1.643-29.458-12.43-37.053-17.35-7.595-4.927-31.679-24.764-31.679-32.359 0-6.328 17.318-16.856 25.735-21.98 1.674-1.016 2.995-1.814 3.754-2.33.868-.587 2.323-1.487 4.114-2.597 7.674-4.772 21.542-13.376 21.894-17.185.43-4.709.266-6.086-5.929-17.709-1.314-2.472-2.855-5.115-4.443-7.853-5.898-10.13-12.5-21.471-11.029-29.598 1.643-9.175 16.035-14.432 28.222-18.89a296.51 296.51 0 0 0 4.435-1.635l12.695-4.764c12.17-4.552 25.687-9.613 27.924-10.637 3.082-1.416 2.284-2.777-7.063-3.661a427.508 427.508 0 0 1-4.537-.47c-11.576-1.227-32.922-3.488-43.294-.602a834.722 834.722 0 0 1-6.727 1.838c-11.655 3.153-25.945 7.017-27.322 9.246-.235.391-.47.727-.696 1.04-1.314 1.862-2.167 3.082-.712 10.998.43 2.362 1.322 7 2.425 12.742 3.207 16.801 8.236 43.005 8.87 48.887.086.829.188 1.619.281 2.386.806 6.57 1.338 10.942-6.296 12.687l-1.995.453c-8.62 1.972-21.252 4.873-25.812 4.873-4.568 0-17.208-2.894-25.828-4.873l-1.987-.453c-7.634-1.745-7.094-6.117-6.289-12.687.094-.767.188-1.565.274-2.386.634-5.89 5.67-32.164 8.894-48.958 1.095-5.71 1.978-10.325 2.409-12.671 1.447-7.916.594-9.136-.72-10.998a29.277 29.277 0 0 1-.704-1.04c-1.36-2.23-15.644-6.093-27.306-9.246-2.402-.649-4.693-1.267-6.727-1.838-10.38-2.894-31.718-.625-43.295.603-1.767.187-3.3.352-4.536.469-9.355.884-10.145 2.245-7.063 3.66 2.229 1.025 15.745 6.086 27.908 10.638 4.67 1.745 9.152 3.419 12.703 4.764 1.431.54 2.918 1.08 4.443 1.643 12.186 4.458 26.579 9.707 28.221 18.882 1.463 8.127-5.139 19.468-11.029 29.598-1.595 2.738-3.128 5.381-4.45 7.853-6.187 11.623-6.352 13-5.921 17.709.344 3.817 14.204 12.413 21.885 17.185 1.792 1.11 3.246 2.01 4.115 2.597.766.516 2.08 1.314 3.754 2.33 8.417 5.116 25.734 15.644 25.734 21.98 0 7.587-24.075 27.432-31.678 32.36-7.588 4.927-28.253 15.706-37.053 17.349-8.792 1.642-22.527-8.3-30.537-23.2-7.532-14.01-1.619-27.839 3.176-39.032l.9-2.112c4.372-10.34-1.823-16.567-6.039-20.79a73.346 73.346 0 0 1-1.369-1.408l-42.16-44.718c-1.236-1.307-2.472-2.503-3.66-3.661-5.695-5.53-10.53-10.231-10.53-22.23 0-14.51 56.233-82.412 56.233-82.412s47.448 9.05 53.846 9.05c5.108 0 14.963-3.387 25.241-6.922 2.605-.892 5.233-1.8 7.822-2.66 12.79-4.263 21.323-4.294 21.323-4.294s8.526.031 21.322 4.294c2.582.86 5.218 1.768 7.822 2.66 10.27 3.535 20.134 6.922 25.234 6.922zm-8.143 240.18c10.028 5.162 17.146 8.823 19.837 10.504 3.48 2.175 1.36 6.281-1.815 8.526-3.168 2.23-45.782 35.191-49.912 38.836l-1.681 1.494c-3.982 3.59-9.066 8.166-12.672 8.166-3.598 0-8.682-4.583-12.672-8.166l-1.666-1.494c-4.145-3.645-46.751-36.607-49.92-38.844-3.167-2.237-5.295-6.335-1.814-8.526 2.69-1.673 9.817-5.342 19.86-10.512l9.543-4.92c15.018-7.76 33.751-14.37 36.677-14.37 2.917 0 21.643 6.603 36.677 14.37a2187.6 2187.6 0 0 0 9.558 4.928v.007z"/>
|
||||
<path fill="url(#b)" d="M370.497 49.759 326.78.214H173.228L129.51 49.76s-38.39-10.654-56.514 7.454c0 0 51.187-4.615 68.779 23.966 0 0 47.448 9.05 53.846 9.05 6.398 0 20.259-5.319 33.055-9.582 12.797-4.263 21.33-4.294 21.33-4.294s8.527.031 21.323 4.294 26.658 9.582 33.056 9.582c6.398 0 53.854-9.05 53.854-9.05 17.591-28.581 68.77-23.966 68.77-23.966-18.123-18.108-56.513-7.454-56.513-7.454z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
1
assets/custom-icons/icons/crowdpear.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="349" height="289" viewBox="0 0 349 289" version="1.1"><path d="" stroke="none" fill="#f05c54" fill-rule="evenodd"/><path d="M 239.500 5.541 C 205.730 9.902, 171.702 22.773, 146.551 40.697 C 125.629 55.608, 103.242 80.036, 90.251 102.131 C 83.727 113.228, 76.448 129.673, 73.378 140.250 C 71.855 145.501, 70.437 150.315, 66.679 163 C 65.294 167.675, 62.815 176.450, 61.169 182.500 C 59.524 188.550, 57.071 197.325, 55.718 202 C 54.365 206.675, 50.676 219.725, 47.521 231 C 44.366 242.275, 40.532 255.775, 39.001 261 C 37.470 266.225, 35.995 271.400, 35.722 272.500 C 35.450 273.600, 34.927 275.223, 34.561 276.107 C 33.745 278.077, 36.477 279.236, 47 281.382 C 97.602 291.700, 156.970 279.284, 201 249.173 C 221.648 235.052, 240.499 215.912, 253.638 195.726 C 262.663 181.860, 272.732 160.215, 276.073 147.500 C 276.507 145.850, 277.599 142.025, 278.500 139 C 279.402 135.975, 280.966 130.575, 281.977 127 C 285.631 114.068, 290.042 98.385, 291.539 93 C 292.380 89.975, 293.680 85.475, 294.427 83 C 295.791 78.484, 298.841 67.263, 301.495 57 C 302.277 53.975, 304.994 44.525, 307.533 36 C 313.697 15.306, 314.432 12.032, 313.198 10.798 C 311.811 9.411, 301.685 7.277, 288.038 5.496 C 274.453 3.723, 253.426 3.743, 239.500 5.541" stroke="none" fill="#ec5c54" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
assets/custom-icons/icons/degiro.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 95 566 376" 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;stroke-linejoin:round;stroke-miterlimit:2;"><g><rect x="0" y="94.5" width="566" height="159.987" style="fill:#009fe3;"/><rect x="0" y="311.513" width="566" height="159.987" style="fill:#009fe3;"/></g></svg>
|
||||
|
After Width: | Height: | Size: 427 B |
1
assets/custom-icons/icons/discourse.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discourse</title><path d="M11.992 0C5.482 0 0 5.278 0 11.791V24l11.99-.012c6.51 0 11.79-5.481 11.79-11.991C23.78 5.486 18.495 0 11.992 0"/><path d="M12.108 4.564c-4.009.002-7.306 3.301-7.306 7.31 0 1.215.302 2.411.881 3.479L4.36 19.607l4.749-1.072a7.312 7.312 0 0 0 3.002.645c4.009 0 7.308-3.299 7.308-7.308 0-4.008-3.297-7.306-7.305-7.308z" fill="#fff9ae"/><path d="M17.822 16.395a7.307 7.307 0 0 1-8.713 2.128L4.36 19.61l4.834-.571a7.306 7.306 0 0 0 8.712-11.613 7.306 7.306 0 0 1-.084 8.969" fill="#00aeef"/><path d="M17.413 15.006a7.307 7.307 0 0 1-8.443 3.027L4.36 19.61l4.749-1.075A7.306 7.306 0 0 0 16.56 6.078a7.305 7.305 0 0 1 .853 8.928" fill="#00a94f"/><path d="M6.12 15.515a7.308 7.308 0 0 1 11.79-8.091 7.308 7.308 0 0 0-12.227 7.929L4.36 19.607z" fill="#f15d22"/><path d="M5.683 15.353A7.308 7.308 0 0 1 16.56 6.078 7.308 7.308 0 0 0 5.232 15.24l-.869 4.37z" fill="#d0232b"/></svg>
|
||||
|
After Width: | Height: | Size: 973 B |
1
assets/custom-icons/icons/dusnet.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>dus.net</title><circle cx="12" cy="21.799" r="2.201"/><path d="M5.467 0a2.202 2.202 0 1 0 .001 4.403A2.202 2.202 0 0 0 5.467 0M12 0a2.201 2.201 0 1 0 0 4.402A2.201 2.201 0 0 0 12 0m6.533 4.402a2.201 2.201 0 1 0 0-4.402 2.201 2.201 0 0 0 0 4.402M5.467 6.533a2.201 2.201 0 1 0 0 4.402 2.201 2.201 0 0 0 0-4.402m6.533 0a2.2 2.2 0 1 0-.001 4.4 2.2 2.2 0 0 0 .001-4.4m6.533 0a2.201 2.201 0 1 0-.001 4.403 2.201 2.201 0 0 0 .001-4.403M5.467 13.065a2.202 2.202 0 1 0 .001 4.403 2.202 2.202 0 0 0-.001-4.403m6.533 0a2.201 2.201 0 1 0 0 4.402 2.201 2.201 0 0 0 0-4.402m6.533 0a2.202 2.202 0 1 0 0 4.404 2.202 2.202 0 0 0 0-4.404" fill="#eb311b"/></svg>
|
||||
|
After Width: | Height: | Size: 721 B |
4
assets/custom-icons/icons/ecloud.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#ec6a55"/>
|
||||
<path d="M256 512.09c.19 141.31 114.69 255.81 256 256a48.36 48.36 0 0 0 0-96.73c-87.84 0-159.27-71.43-159.27-159.27S424.16 352.81 512 352.81c70.92 0 131.25 46.65 151.75 110.91H512a48.36 48.36 0 1 0 0 96.73h207.64A48.43 48.43 0 0 0 768 512.09c-.19-141.33-114.67-255.89-256-256.17-141.16.17-256 115-256 256.17" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 468 B |
3
assets/custom-icons/icons/epic_games.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" fill-rule="evenodd">
|
||||
<path d="m191.196 323.447-6.552 16.497h12.986l-6.434-16.497zm38.469-171.75v-48.161c0-7.677-3.528-11.222-10.886-11.222h-11.977v70.622h11.977c7.358 0 10.886-3.561 10.886-11.221v-.017zM426.175.19H73.79c-28.592 0-39.108 10.533-39.108 39.108v344.81l.42 9.02c.639 6.25.79 12.297 6.586 19.15a121.454 121.454 0 0 0 6.5 5.074l8.954 4.166 173.514 72.688c9.02 4.116 12.767 5.745 19.318 5.594h.05c6.552.134 10.298-1.478 19.32-5.594l173.513-72.688 8.954-4.166 6.5-5.073c5.796-6.888 5.93-12.935 6.586-19.15a88.697 88.697 0 0 0 .42-9.022V39.315c0-28.592-10.55-39.108-39.108-39.108h-.033V.19zM271.948 66.58h29.146v191.57h-29.146V66.579zm1.965 238.222h16.967v57.955h-15.992v-33.295L260.07 352.14h-.336l-14.732-22.51v33.126h-15.74v-57.955h16.966l13.826 22.46 13.825-22.46h.034zM177.656 66.579h45.743c23.686 0 35.411 11.759 35.411 35.58v50.9c0 23.803-11.708 35.579-35.411 35.579h-16.597v69.53h-29.146V66.578zm-79.44 0h64.825V93.12h-35.68v54.175h34.32v26.542h-34.32v57.754h36.235v26.542h-65.38V66.58zm55.72 288.416c-6.131 5.04-14.665 8.937-25.164 8.937-18.059 0-31.548-12.43-31.548-29.985v-.168c0-16.883 13.237-30.154 31.212-30.154 10.197 0 17.387 3.141 23.518 8.45l-9.44 11.339c-4.15-3.477-8.283-5.46-13.994-5.46-8.366 0-14.817 7.022-14.817 15.909v.168a15.287 15.287 0 0 0 15.74 16.076c3.898 0 6.888-.84 9.274-2.402v-7.022h-11.423v-11.76h26.66v26.09l-.018-.018zm29.65-50.597h15.488l24.66 58.375h-17.218l-4.233-10.365h-22.376l-4.15 10.365h-16.882l24.66-58.375h.052zm65.078 155.068L166.99 431.38h166.828l-85.153 28.087zm99.902-96.676H301.53v-57.955h46.616v13.657h-30.708v8.786h27.835v12.683h-27.835v9.188h31.145v13.658-.034l-.017.017zM316.53 224.218V100.512c0-23.804 11.708-35.58 35.411-35.58h14.162c23.686 0 35.142 11.508 35.142 35.311v39.141h-28.59V101.89c0-7.677-3.562-11.222-10.886-11.222h-4.906c-7.61 0-11.17 3.561-11.17 11.222v120.95c0 7.677 3.56 11.222 11.17 11.222h5.46c7.358 0 10.885-3.562 10.885-11.222v-43.223H401.8v44.6c0 23.804-11.709 35.597-35.412 35.597h-14.447c-23.686 0-35.411-11.759-35.411-35.596zm86.765 120.681c0 11.843-9.357 18.865-23.434 18.865-10.281 0-20.041-3.225-27.164-9.609l8.937-10.7a29.734 29.734 0 0 0 18.781 6.938c4.317 0 6.636-1.479 6.636-3.982v-.168c0-2.402-1.899-3.729-9.777-5.543-12.347-2.822-21.872-6.3-21.872-18.227v-.168c0-10.785 8.534-18.562 22.46-18.562 9.86 0 17.554 2.654 23.854 7.693l-8.03 11.34c-5.291-3.73-11.087-5.712-16.244-5.712-3.898 0-5.796 1.646-5.796 3.73v.167c0 2.654 1.982 3.814 10.029 5.628 13.321 2.906 21.62 7.19 21.62 18.058v.252z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
6
assets/custom-icons/icons/esketit.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="1000" height="1000" viewBox="0 0 1000 1000" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="1000" height="1000" fill="#0B042C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M530.713 484.942C531.477 480.62 532.627 476.603 534.17 472.891C537.226 465.545 541.723 459.817 547.668 455.715C553.609 451.613 561.078 449.562 570.079 449.562C578.905 449.562 586.163 451.445 591.853 455.202C597.538 458.963 601.824 464.219 604.714 470.969C606.489 475.125 607.714 479.786 608.396 484.942H530.713ZM607.134 434.692C597.369 428.198 585.017 424.949 570.079 424.949C558.702 424.949 548.77 426.959 540.282 430.974C531.791 434.992 524.747 440.375 519.146 447.126C513.543 453.881 509.339 461.483 506.539 469.943C503.738 478.404 502.337 487.165 502.337 496.223V500.838C502.337 509.726 503.738 518.399 506.539 526.859C509.339 535.321 513.543 542.928 519.146 549.678C524.747 556.432 531.918 561.855 540.664 565.958C549.406 570.059 559.721 572.111 571.607 572.111C582.641 572.111 592.446 570.272 601.021 566.598C609.591 562.924 616.723 557.585 622.412 550.574C628.099 543.568 631.964 535.364 634 525.962H607.261C605.732 531.775 601.868 536.819 595.673 541.089C589.473 545.363 581.452 547.498 571.607 547.498C561.929 547.498 553.991 545.407 547.795 541.217C541.596 537.03 537.012 531.218 534.043 523.783C531.736 518.011 530.337 511.641 529.822 504.684H636.293V493.146C636.293 481.353 633.873 470.288 629.034 459.945C624.196 449.605 616.894 441.189 607.134 434.692ZM483.864 429.564H453.813L397.785 497.295V388.77H369.008V567.495H397.785V503.145H414.595L463.999 567.495H496.852L434.99 488.47L483.864 429.564ZM327.502 498.787C318.501 491.953 306.364 487.762 291.084 486.224L277.332 484.687C269.52 483.832 263.918 481.995 260.523 479.174C257.126 476.354 255.43 472.553 255.43 467.764C255.43 462.297 257.723 457.766 262.306 454.177C266.891 450.587 273.596 448.793 282.425 448.793C291.421 448.793 298.253 450.676 302.926 454.433C307.593 458.195 310.012 462.637 310.184 467.764H336.668C336.328 453.921 331.194 443.409 321.262 436.23C311.329 429.052 298.382 425.462 282.425 425.462C271.896 425.462 262.644 427.174 254.667 430.59C246.684 434.01 240.405 438.882 235.821 445.204C231.236 451.529 228.944 459.221 228.944 468.278C228.944 479.903 233.06 489.089 241.296 495.838C249.529 502.592 260.606 506.734 274.53 508.273L288.282 509.554C297.617 510.58 304.326 512.591 308.4 515.58C312.476 518.572 314.512 522.718 314.512 528.014C314.512 533.827 311.796 538.653 306.364 542.499C300.928 546.345 293.205 548.267 283.189 548.267C271.984 548.267 263.918 546.089 258.995 541.73C254.068 537.371 251.356 532.629 250.845 527.502H224.615C224.953 541.345 230.218 552.158 240.405 559.933C250.591 567.708 264.853 571.599 283.189 571.599C294.561 571.599 304.621 569.759 313.367 566.086C322.109 562.412 328.902 557.241 333.741 550.575C338.579 543.909 340.999 536.218 340.999 527.502C340.999 515.194 336.499 505.625 327.502 498.787ZM699.957 389.056H672.707V429.564H649.787V451.099H672.707V518.784C672.707 531.434 674.7 541.433 678.691 548.779C682.679 556.131 688.535 561.342 696.263 564.419C703.987 567.495 713.454 569.034 724.66 569.034H745.032V543.908H722.113C714.982 543.908 709.506 541.946 705.687 538.012C701.867 534.082 699.957 528.526 699.957 521.348V451.099H745.032V429.564H699.957V389.056ZM929.153 451.099V429.564H884.078V389.056H856.828V429.564H833.907V451.099H856.828V518.784C856.828 531.434 858.821 541.433 862.812 548.779C866.8 556.131 872.656 561.342 880.383 564.419C888.108 567.495 897.574 569.034 908.78 569.034H929.153V543.908H906.234C899.103 543.908 893.627 541.946 889.808 538.012C885.986 534.082 884.078 528.526 884.078 521.348V451.099H929.153ZM764.131 451.869H784.505V567.496H813.282V429.564H764.131V451.869ZM270.186 409.649H297.239V389.009H270.186V409.649ZM785.737 409.649H812.791V389.009H785.737V409.649ZM98.1558 484.942C98.9189 480.62 100.07 476.603 101.613 472.891C104.669 465.545 109.165 459.817 115.111 455.715C121.05 451.613 128.52 449.562 137.522 449.562C146.348 449.562 153.605 451.445 159.296 455.202C164.982 458.963 169.267 464.219 172.156 470.969C173.931 475.125 175.155 479.786 175.839 484.942H98.1558ZM174.576 434.692C164.811 428.198 152.46 424.949 137.522 424.949C126.144 424.949 116.212 426.959 107.725 430.974C99.2339 434.992 92.1892 440.375 86.5877 447.126C80.9849 453.881 76.7825 461.483 73.9817 469.943C71.1809 478.404 69.7793 487.165 69.7793 496.223V500.838C69.7793 509.726 71.1809 518.399 73.9817 526.859C76.7825 535.321 80.9849 542.928 86.5877 549.678C92.1892 556.432 99.3609 561.855 108.106 565.958C116.849 570.059 127.162 572.111 139.049 572.111C150.084 572.111 159.888 570.272 168.464 566.598C177.035 562.924 184.165 557.585 189.855 550.574C195.541 543.568 199.405 535.364 201.443 525.962H174.703C173.175 531.775 169.311 536.819 163.115 541.089C156.915 545.363 148.895 547.498 139.049 547.498C129.372 547.498 121.433 545.407 115.238 541.217C109.038 537.03 104.455 531.218 101.485 523.783C99.1778 518.011 97.7786 511.641 97.2634 504.684H203.736V493.146C203.736 481.353 201.316 470.288 196.476 459.945C191.638 449.605 184.337 441.189 174.576 434.692ZM270.186 608.075H297.239V588.083H270.186V608.075Z"
|
||||
fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.0 KiB |
1
assets/custom-icons/icons/estateguru.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="400" viewBox="0 0 400 400" version="1.1"><path d="M 187 53.170 C 181.955 55.065, 176.921 59.801, 126.212 110.368 C 80.783 155.668, 70.440 166.481, 68.212 171 L 65.500 176.500 65.500 231 L 65.500 285.500 68.432 291.290 C 72.004 298.344, 78.253 304.290, 85.825 307.840 L 91.500 310.500 199.500 310.500 L 307.500 310.500 314 307.387 C 322.095 303.510, 327.422 298.258, 331.131 290.500 L 333.998 284.500 333.998 231 L 333.998 177.500 330.937 171 C 328.156 165.096, 323.132 159.746, 276.187 112.685 C 247.759 84.186, 222.475 59.389, 220 57.580 C 210.808 50.860, 197.783 49.120, 187 53.170 M 194.500 75.146 C 191.385 76.837, 91.050 177.162, 89.435 180.200 C 88.246 182.438, 88 191.073, 88 230.549 C 88 284.367, 87.895 283.540, 95.097 286.422 C 98.598 287.823, 110.384 288, 200.052 288 C 285.090 288, 301.602 287.775, 304.466 286.579 C 311.086 283.813, 311 284.557, 311 229.810 L 311 180.521 257.740 127.260 C 205.048 74.569, 204.436 74, 200.490 74.030 C 198.295 74.047, 195.600 74.549, 194.500 75.146 M 196.670 105.737 C 194.511 106.051, 184.441 115.634, 154.170 146.182 C 132.352 168.200, 113.715 187.404, 112.757 188.857 C 111.179 191.249, 111.013 194.987, 111.007 228.250 L 111 265 122.500 265 L 134 265 134.049 232.250 L 134.098 199.500 166.899 166.262 L 199.700 133.024 232.350 166.062 L 265 199.099 265 232.050 L 265 265 276.500 265 L 288 265 287.965 228.250 C 287.939 201.311, 287.605 190.933, 286.715 189.376 C 284.568 185.622, 206.799 107.069, 204.610 106.444 C 201.087 105.438, 199.623 105.308, 196.670 105.737 M 178.674 187.570 L 158 209.191 158 237.128 L 158 265.066 169.250 264.783 L 180.500 264.500 181 241.405 L 181.500 218.310 190.485 208.543 L 199.469 198.776 209.235 208.895 L 219 219.015 219 242.008 L 219 265 230.500 265 L 242 265 241.947 237.250 L 241.895 209.500 221.381 188 C 210.098 176.175, 200.525 166.376, 200.108 166.225 C 199.690 166.073, 190.045 175.679, 178.674 187.570" stroke="none" fill="#eef8fb" fill-rule="evenodd"/><path d="M -0 200.002 L -0 400.005 200.250 399.752 L 400.500 399.500 400.752 199.750 L 401.005 0 200.502 0 L 0 0 -0 200.002 M 0.490 200.500 C 0.490 310.500, 0.607 355.352, 0.750 300.170 C 0.893 244.989, 0.893 154.989, 0.750 100.170 C 0.607 45.352, 0.490 90.500, 0.490 200.500 M 187 53.170 C 181.955 55.065, 176.921 59.801, 126.212 110.368 C 80.783 155.668, 70.440 166.481, 68.212 171 L 65.500 176.500 65.500 231 L 65.500 285.500 68.432 291.290 C 72.004 298.344, 78.253 304.290, 85.825 307.840 L 91.500 310.500 199.500 310.500 L 307.500 310.500 314 307.387 C 322.095 303.510, 327.422 298.258, 331.131 290.500 L 333.998 284.500 333.998 231 L 333.998 177.500 330.937 171 C 328.156 165.096, 323.132 159.746, 276.187 112.685 C 247.759 84.186, 222.475 59.389, 220 57.580 C 210.808 50.860, 197.783 49.120, 187 53.170 M 194.500 75.146 C 191.385 76.837, 91.050 177.162, 89.435 180.200 C 88.246 182.438, 88 191.073, 88 230.549 C 88 284.367, 87.895 283.540, 95.097 286.422 C 98.598 287.823, 110.384 288, 200.052 288 C 285.090 288, 301.602 287.775, 304.466 286.579 C 311.086 283.813, 311 284.557, 311 229.810 L 311 180.521 257.740 127.260 C 205.048 74.569, 204.436 74, 200.490 74.030 C 198.295 74.047, 195.600 74.549, 194.500 75.146 M 196.670 105.737 C 194.511 106.051, 184.441 115.634, 154.170 146.182 C 132.352 168.200, 113.715 187.404, 112.757 188.857 C 111.179 191.249, 111.013 194.987, 111.007 228.250 L 111 265 122.500 265 L 134 265 134.049 232.250 L 134.098 199.500 166.899 166.262 L 199.700 133.024 232.350 166.062 L 265 199.099 265 232.050 L 265 265 276.500 265 L 288 265 287.965 228.250 C 287.939 201.311, 287.605 190.933, 286.715 189.376 C 284.568 185.622, 206.799 107.069, 204.610 106.444 C 201.087 105.438, 199.623 105.308, 196.670 105.737 M 178.674 187.570 L 158 209.191 158 237.128 L 158 265.066 169.250 264.783 L 180.500 264.500 181 241.405 L 181.500 218.310 190.485 208.543 L 199.469 198.776 209.235 208.895 L 219 219.015 219 242.008 L 219 265 230.500 265 L 242 265 241.947 237.250 L 241.895 209.500 221.381 188 C 210.098 176.175, 200.525 166.376, 200.108 166.225 C 199.690 166.073, 190.045 175.679, 178.674 187.570" stroke="none" fill="#244cf9" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 7.9 KiB |
6
assets/custom-icons/icons/gitlab.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-labelledby="tanukiHomeTablet" viewBox="0 0 500 500">
|
||||
<path fill="#E24329" d="m491.105 199.768-.699-1.787-67.837-177.004a17.733 17.733 0 0 0-6.97-8.421 18.14 18.14 0 0 0-20.938 1.114 17.747 17.747 0 0 0-6.011 9.147L342.785 163.18H157.23L111.367 22.817a17.75 17.75 0 0 0-6.011-9.147 17.983 17.983 0 0 0-20.911-1.114 17.646 17.646 0 0 0-6.997 8.421L9.533 198.214l-.699 1.762a126.167 126.167 0 0 0 41.77 145.65l.259.182.596.466 103.466 77.45 51.15 38.74 31.095 23.552a21.038 21.038 0 0 0 25.367 0l31.094-23.552 51.175-38.74 104.063-77.916.284-.208a126.13 126.13 0 0 0 45.357-65.902 126.126 126.126 0 0 0-3.405-79.93z"/>
|
||||
<path fill="#FC6D26" d="m491.108 199.768-.699-1.787a229.975 229.975 0 0 0-91.313 41.07L250.128 351.947c50.787 38.403 94.993 71.776 94.993 71.776l104.062-77.916.285-.208a126.138 126.138 0 0 0 41.64-145.832z"/>
|
||||
<path fill="#FCA326" d="m155.005 423.724 51.072 38.74 31.094 23.552a21.041 21.041 0 0 0 25.368 0l31.094-23.552 51.177-38.74s-44.207-33.373-94.994-71.776l-94.811 71.776z"/>
|
||||
<path fill="#FC6D26" d="M100.847 239.155A229.413 229.413 0 0 0 9.534 198.11l-.7 1.762a126.16 126.16 0 0 0 41.77 145.754l.26.18.595.467 103.466 77.45 95.2-71.776-149.278-112.793z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
1
assets/custom-icons/icons/inwx.svg
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
4
assets/custom-icons/icons/kagi.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M410.121 0H101.879C45.6125 0 0 45.6125 0 101.879V410.121C0 466.388 45.6125 512 101.879 512H410.121C466.388 512 512 466.388 512 410.121V101.879C512 45.6125 466.388 0 410.121 0Z" fill="#FFB319"/>
|
||||
<path d="M297.731 375.79H211.319C187.568 375.79 183.953 350.318 188.38 340.093C190.491 335.241 195.271 328.218 199.795 322.48C216.994 331.976 236.322 336.947 255.968 336.927C286.917 336.891 316.588 324.58 338.47 302.695C360.354 280.81 372.661 251.138 372.694 220.189C372.701 203.83 369.261 187.652 362.598 172.711C355.935 157.769 346.199 144.4 334.023 133.473L336.704 130.756C339.84 127.566 343.656 125.126 347.868 123.618C352.079 122.11 356.576 121.573 361.025 122.047L375.071 123.539V66.3224H350.52C337.213 66.3293 324.213 70.3151 313.189 77.7681C302.166 85.2211 293.623 95.7999 288.658 108.146C278.02 105.031 266.992 103.45 255.908 103.452C224.958 103.487 195.286 115.798 173.4 137.682C151.516 159.568 139.205 189.239 139.17 220.189C139.139 245.073 147.099 269.309 161.877 289.329L154.963 294.654C153.931 295.443 152.755 296.437 151.543 297.578C127.89 319.629 117.045 348.523 123.243 380.654C126.64 398.255 141.584 416.535 156.795 426.227C167.619 433.032 180.132 436.671 192.917 436.731L289.798 432.801C295.179 432.788 300.475 434.138 305.194 436.723C309.913 439.308 313.901 443.046 316.787 447.587L329.45 470.44L389.505 450.365L379.232 427.998C372.064 412.411 360.575 399.208 346.13 389.955C331.684 380.701 314.887 375.786 297.731 375.79ZM255.968 164.355C270.758 164.381 284.936 170.268 295.394 180.727C305.853 191.185 311.739 205.363 311.765 220.153C311.739 234.943 305.853 249.12 295.394 259.579C284.936 270.038 270.758 275.924 255.968 275.95C241.178 275.924 227.001 270.038 216.542 259.579C206.083 249.12 200.196 234.943 200.17 220.153C200.196 205.363 206.083 191.185 216.542 180.727C227.001 170.268 241.178 164.381 255.968 164.355Z" fill="#191919"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
4
assets/custom-icons/icons/kite.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" viewBox="0 0 500 500">
|
||||
<path fill="#f6461a" d="M166.667 83.333 0 250l166.667 166.667L333.333 250 500 83.333H166.667z" class="cls-1"/>
|
||||
<path fill="#db342c" d="M166.667 416.667 333.333 250 500 416.667H166.667z" class="cls-2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 296 B |
4
assets/custom-icons/icons/koofr.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#263238"/>
|
||||
<path d="M768 554.7V640c0 23.6-19.1 42.7-42.7 42.7H298.7c-23.6 0-42.7-19.1-42.7-42.7v-85.3c0-23.6 19.1-42.7 42.7-42.7h426.7c23.5 0 42.6 19.1 42.6 42.7zm-42.7-71.1c9.4 0 18.7 1.9 27.4 5.5l-85.8-128.7c-7.9-11.9-21.2-19-35.5-19H392.6c-14.3 0-27.6 7.1-35.5 19L271.3 489c8.7-3.6 18-5.5 27.4-5.5h426.6zm-42.6 85.3c-15.7 0-28.4 12.7-28.4 28.4s12.7 28.4 28.4 28.4 28.4-12.7 28.4-28.4-12.7-28.4-28.4-28.4zm-85.4 0c-15.7 0-28.4 12.7-28.4 28.4s12.7 28.4 28.4 28.4 28.4-12.7 28.4-28.4c.1-15.7-12.7-28.4-28.4-28.4z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 662 B |
1
assets/custom-icons/icons/mintos.svg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
4
assets/custom-icons/icons/pCloud.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||
<circle cx="512" cy="512" r="512" style="fill:#1ebcc5"/>
|
||||
<path d="M550.35 470.76a16.57 16.57 0 0 1-16.56 16.57h-41.57V454.2h41.56a16.56 16.56 0 0 1 16.57 16.56Zm92.14 17.67a132 132 0 1 1-132-132 132 132 0 0 1 131.99 132Zm-52.81-17.69a56 56 0 0 0-56.06-56h-62.5A21.28 21.28 0 0 0 449.83 436v115.42a21.28 21.28 0 1 0 42.56 0v-24.56h41.22v-.08a56.05 56.05 0 0 0 56.06-56.04ZM805.06 583.1a94.06 94.06 0 0 1-92.18 94v.16H358.36a139.36 139.36 0 0 1-17.51-277.62 187.32 187.32 0 0 0-17.08 78c0 1.52.14 3 .18 4.53 3.58-100.84 86.4-181.49 188.1-181.49 102 0 185 81.11 188.11 182.33.07-1.79.16-3.57.16-5.37a185.38 185.38 0 0 0-16.94-77.76 78.53 78.53 0 0 1 45.7 147.8 79.17 79.17 0 0 0 40.82-37.94 93.92 93.92 0 0 1 35.16 73.36Zm-143-94.45a151.53 151.53 0 1 0-151.49 151.54 151.52 151.52 0 0 0 151.52-151.53Z" style="fill:#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
30
assets/custom-icons/icons/pingvinshare.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 943.11 911.62"><script xmlns=""/>
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #37474f;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #46509e;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<ellipse class="cls-3" cx="471.56" cy="454.28" rx="471.56" ry="454.28"/>
|
||||
<g>
|
||||
<g>
|
||||
<ellipse class="cls-2" cx="471.56" cy="390.28" rx="233.66" ry="207"/>
|
||||
<path class="cls-2" d="m705.22,848.95c-36.69,21.14-123.09,64.33-240.64,62.57-109.54-1.63-190.04-41.45-226.68-62.57v-454.19h467.33v454.19Z"/>
|
||||
</g>
|
||||
<path class="cls-1" d="m658.81,397.7v475.8c-36.98,15.7-98.93,36.54-177.98,38.04-88.67,1.69-157.75-21.73-196.2-38.04v-475.8c0-95.55,83.77-173.02,187.09-173.02s187.09,77.47,187.09,173.02Z"/>
|
||||
<polygon class="cls-3" points="565.02 431.68 471.56 514.49 378.09 431.68 565.02 431.68"/>
|
||||
<ellipse class="cls-2" cx="378.09" cy="369.58" rx="23.37" ry="20.7"/>
|
||||
<ellipse class="cls-2" cx="565.02" cy="369.58" rx="23.37" ry="20.7"/>
|
||||
<path class="cls-2" d="m658.49,400.63c0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45c0,11.14,2.81,21.65,7.9,31.05h-62.54c5.1-9.4,7.9-19.91,7.9-31.05,0-40.04-36.59-72.45-81.78-72.45s-81.78,32.41-81.78,72.45l-46.73-10.35c0-114.32,104.63-207,233.66-207s233.66,92.69,233.66,207l-46.73,10.35Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
6
assets/custom-icons/icons/proxmox.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 388.755L126.571 249.665L0 110.571C13.9095 96.6616 33.3816 87.8523 54.7141 87.8523C77.4297 87.8523 97.3643 97.5937 111.274 112.89L181.281 189.854L235.992 249.665L181.281 309.942L111.274 386.902C97.3643 402.202 77.4297 411.94 54.7141 411.94C33.3816 411.94 13.9095 403.127 0 388.755ZM500 388.757L373.43 249.667L500 110.573C486.091 96.6633 466.619 87.8536 445.286 87.8536C422.571 87.8536 402.636 97.5956 388.726 112.892L318.719 189.856L264.008 249.667L318.719 309.943L388.726 386.904C402.636 402.204 422.571 411.942 445.286 411.942C466.619 411.942 486.091 403.128 500 388.757Z" fill="#E57000"/>
|
||||
<g style="mix-blend-mode:difference">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M249.934 235.708L299.87 180.692L415.394 53.7358C402.7 41.0404 384.926 33 365.456 33C344.722 33 326.528 41.8913 313.83 55.8522L249.934 126.1L185.607 55.8522C172.491 41.4659 155.138 33 134.4 33C114.941 33 97.163 41.0404 84.4681 53.7358L199.996 180.692L249.934 235.708ZM249.937 263.615L299.873 318.631L415.398 445.588C402.703 458.283 384.929 466.323 365.459 466.323C344.725 466.323 326.531 457.432 313.834 443.471L249.937 373.224L185.61 443.471C172.494 457.857 155.141 466.323 134.403 466.323C114.944 466.323 97.1661 458.283 84.4713 445.588L200 318.631L249.937 263.615Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
4
assets/custom-icons/icons/rust_language_forum.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
|
||||
<path d="M250 17.376C121.754 17.376 17.376 121.803 17.376 250S121.803 482.624 250 482.624 482.624 378.197 482.624 250 378.197 17.376 250 17.376zm-.344 20.669a15.256 15.305 0 0 1 14.861 15.305 15.305 15.305 0 0 1-30.61 0 15.305 15.305 0 0 1 15.749-15.305zm35.038 25.196a188.334 188.334 0 0 1 128.935 91.83l-18.06 40.747c-3.1 7.037.098 15.305 7.086 18.454l34.744 15.404a188.334 188.334 0 0 1 .393 32.676h-19.34c-1.92 0-2.707 1.28-2.707 3.15v8.858c0 20.866-11.761 25.442-22.096 26.574-9.842 1.132-20.718-4.134-22.096-10.137-5.807-32.628-15.452-39.567-30.708-51.624 18.947-12.007 38.631-29.773 38.631-53.493 0-25.64-17.568-41.78-29.527-49.704-16.83-11.072-35.432-13.287-40.452-13.287h-199.8A188.334 188.334 0 0 1 215.06 63.241l23.572 24.704c5.315 5.561 14.124 5.807 19.685.443l26.377-25.147zM67.178 176.527a15.305 15.305 0 0 1 14.862 15.305 15.305 15.305 0 0 1-30.61 0 15.305 15.305 0 0 1 15.748-15.305zm364.906.689a15.305 15.305 0 0 1 14.862 15.305 15.305 15.305 0 0 1-30.61 0 15.305 15.305 0 0 1 15.748-15.305zm-336.068 2.46h26.673V299.95H68.851a188.334 188.334 0 0 1-6.102-71.898l32.972-14.666c7.037-3.15 10.236-11.368 7.086-18.405l-6.79-15.305zm111.317 1.28h63.533c3.297 0 23.179 3.79 23.179 18.7 0 12.352-15.256 16.782-27.805 16.782h-58.956l.05-35.482zm0 86.416h48.67c4.43 0 23.77 1.28 29.922 25.984 1.919 7.578 6.2 32.283 9.104 40.206 2.903 8.858 14.665 26.574 27.214 26.574h79.428a188.334 188.334 0 0 1-17.421 20.177l-32.332-6.939c-7.53-1.624-14.96 3.199-16.585 10.728l-7.677 35.826a188.334 188.334 0 0 1-157.035-.738l-7.677-35.826c-1.624-7.53-9.005-12.352-16.535-10.728l-31.643 6.791a188.334 188.334 0 0 1-16.338-19.291h153.885c1.722 0 2.904-.295 2.904-1.92V303.79c0-1.575-1.182-1.92-2.904-1.92h-45.029l.05-34.497zm-71.012 124.653a15.305 15.305 0 0 1 14.862 15.305 15.305 15.305 0 0 1-30.61 0 15.305 15.305 0 0 1 15.748-15.305zm226.62.69a15.305 15.305 0 0 1 14.862 15.304 15.305 15.305 0 0 1-30.61 0 15.305 15.305 0 0 1 15.748-15.305z"/>
|
||||
<path fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M469.632 250A219.632 219.632 0 0 1 250 469.632 219.632 219.632 0 0 1 30.368 250 219.632 219.632 0 0 1 250 30.368 219.632 219.632 0 0 1 469.632 250zm-4.133-21.21L499.75 250l-34.251 21.21 29.428 27.51-37.696 14.123 23.523 32.726-39.812 6.496 16.732 36.712-40.305-1.427 9.252 39.271-39.27-9.252 1.426 40.305-36.712-16.732-6.496 39.812-32.726-23.523-14.123 37.696-27.51-29.428L250 499.75l-21.21-34.251-27.51 29.428-14.123-37.696-32.726 23.523-6.496-39.812-36.712 16.732 1.427-40.305-39.271 9.252 9.252-39.27-40.305 1.426 16.732-36.712-39.812-6.496 23.523-32.726L5.073 298.72 34.5 271.21.25 250l34.251-21.21-29.428-27.51 37.696-14.123-23.523-32.726 39.812-6.496-16.732-36.712 40.305 1.427-9.252-39.271 39.27 9.252-1.426-40.305 36.712 16.732 6.496-39.812 32.726 23.523L201.28 5.073 228.79 34.5 250 .25l21.21 34.251 27.51-29.428 14.123 37.696 32.726-23.523 6.496 39.812 36.712-16.732-1.427 40.305 39.271-9.252-9.252 39.27 40.305-1.426-16.732 36.712 39.812 6.496-23.523 32.726 37.696 14.123-29.428 27.51z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
1
assets/custom-icons/icons/service-bw.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg rols="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>service-bw</title><path fill="#f8dd15" d="M7.926 7.962c-.582.086-.083.499-.083.499l.618-.477s.035-.09-.535-.022 M9.571 10.864c.144-1.248-1.379-2.88-1.379-2.88l.592-.794-1.478.407s-1.581-.691-3.946.793a1.717 1.717 0 0 0 0 .989c.097.322.925-.48 1.107.154-.269.078-.548.12-.829.125a1.239 1.239 0 0 1-.758-.196 2.065 2.065 0 0 1-.48-.96c-.073-.356-.342 1.447 1.21 1.447.293.026.588-.004.87-.09-.051.858-.829.215-.829.215v.988s1.258-.163 1.488-.339c-.064.221-.173.819-.173.819l-2.025-.08S.691 9.478 0 11.856l1.581.099 3.651 2.378-1.677 1.28s-2.56-.858-2.464 1.123l3.251.058 5.095-1.184v-.586s1.449-.099 1.747-.099a15.308 15.308 0 0 0 1.862-.778v-3.232c-1.724.202-3.193.269-3.475-.051m-3.859-2.88-.618.493s-.499-.416.084-.499c.582-.084.534.022.534.022z M11.546 8.883c-.298-.601.492-1.683.492-1.683s-2.169.595-1.776 1.981c.304 1.069 2.01.96 2.778.854v-.96c-.618.103-1.334.135-1.494-.192"/><path d="M13.28 14.024v4.056h-.32V5.92h.32v3.146c.247-.045.493-.099.736-.164 0 0 5.203-1.52 5.622-1.6.42-.08 2.269-.396 2.762 1.485.176 1.415-.48 1.776-.995 2.279.883.08 1.184 2.393 1.184 3.068 1.062-.371 1.283.298 1.382.298.099 0-.089 2.179-.089 2.179l-2.762-.096s-.352-1.017 1.28-1.081c-2.275-.692-3.261-2.068-3.261-2.068s-.889 1.104-1.184 1.6c-.294.496-.393 1.642-.393 1.642H14.01c.06-.55.182-.858.588-1.046l1.476.041.387-2.771-.413-.099s-1.449.677-2.768 1.291m0-3.995v.845c2.485-.308 5.388-.857 5.958-.912a1.866 1.866 0 0 1 1.479.396s.598.496.79-.988c.192-1.485-1.974-1.204-2.368-1.088-.393.115-5.705 1.718-5.705 1.718s-.057.012-.154.029"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
assets/custom-icons/icons/sipgate.svg
Normal file
|
After Width: | Height: | Size: 14 KiB |
27
assets/custom-icons/icons/tcpshield.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 686 802" width="686" height="802">
|
||||
<title>TCPShield</title>
|
||||
<style>
|
||||
.s0 { fill: #ffffff;stroke: #414042;stroke-miterlimit:10 }
|
||||
.s1 { fill: #ffffff }
|
||||
</style>
|
||||
<g id="Layer 1">
|
||||
<g id="Layer 2 1 ">
|
||||
</g>
|
||||
<g id="Layer 1 1 ">
|
||||
<g id="<Group>">
|
||||
<path id="<Path>" class="s0" d="m205.2 141.5c6.8-6.1 13.6-12 20.5-17.8 7.5-6.1 15-12.2 22.6-17.9 3.8-2.9 7.7-5.7 11.5-8.5 1.9-1.4 3.9-2.7 5.8-4.1 1.9-1.3 3.9-2.7 5.8-4 2-1.3 3.9-2.6 5.9-3.9 2-1.3 3.9-2.5 5.9-3.8 3.9-2.6 7.9-4.9 11.9-7.4 2-1.2 4-2.3 6-3.5l3-1.7 3-1.7q3-1.7 6-3.3c2-1.1 4-2.1 6.1-3.2 1-0.5 2-1.1 3-1.6q1.5-0.8 3-1.5c2-1 4-2 6.1-3 4.1-1.9 8.1-3.8 12.2-5.6 4.1-1.8 8.2-3.4 12.3-5.1 2-0.8 4.1-1.5 6.1-2.3 1-0.4 2-0.8 3.1-1.1 1-0.3 2.1-0.7 3.1-1 8.2-2.9 16.4-5.2 24.5-7.3 2-0.5 4.1-1 6.1-1.4 2-0.4 4.1-0.8 6.1-1.2 2-0.4 4-0.7 6.1-1.1 2-0.3 4-0.7 6-0.9 16-2.2 31.7-2.4 46.5-0.6 1.8 0.3 3.7 0.5 5.5 0.8 1.8 0.3 3.6 0.7 5.4 1 1.8 0.3 3.5 0.8 5.3 1.2 1.7 0.4 3.5 0.8 5.2 1.3q2.5 0.7 5.1 1.5c1.7 0.6 3.3 1.2 4.9 1.8q1.2 0.4 2.4 0.9c0.8 0.3 1.6 0.7 2.4 1 0.8 0.3 1.6 0.7 2.4 1 0.8 0.3 1.6 0.6 2.3 1 12.3 5.8 23.2 13 32.4 21.3 9.3 8.2 17.2 17.2 23.8 26.3 6.6 9.1 12.1 18.3 16.7 27 1.1 2.2 2.2 4.4 3.2 6.5 0.5 1.1 1.1 2.1 1.5 3.2 0.5 1.1 0.9 2.1 1.4 3.2 0.9 2.1 1.8 4.1 2.7 6.1 0.8 2 1.6 4 2.4 5.9 1.6 3.8 2.9 7.6 4.2 11 1.3 3.5 2.3 6.8 3.3 9.9 0.5 1.5 1 3 1.4 4.5 0.5 1.4 0.8 2.8 1.2 4.1 0.8 2.6 1.4 5 2 7.1 0.3 1 0.6 2.1 0.8 3.1 0.3 1 0.5 1.9 0.7 2.8 0.4 1.7 0.8 3.2 1.1 4.3 0 0.2 0.1 0.3 0.1 0.4l5.3 1.9 26.4-7.1c0 0-0.3-1.2-1-3.5-0.3-1.2-0.7-2.6-1.2-4.3-0.2-0.9-0.5-1.8-0.8-2.8-0.3-1-0.6-2-0.9-3.2-0.7-2.4-1.6-5-2.5-7.9-0.5-1.4-0.9-3-1.5-4.5-0.5-1.5-1.1-3.1-1.7-4.8-1.2-3.3-2.4-6.9-3.9-10.7-1.5-3.8-3-7.8-4.9-12-0.9-2.1-1.8-4.3-2.8-6.5-1-2.2-2.1-4.4-3.2-6.7-0.5-1.1-1.1-2.3-1.7-3.5-0.6-1.2-1.2-2.3-1.8-3.5-1.2-2.4-2.5-4.8-3.8-7.2-5.5-9.7-11.9-20-19.8-30.3-7.9-10.3-17.4-20.7-28.8-30.2-11.3-9.6-24.7-18-39.7-24.6-0.9-0.4-1.9-0.8-2.8-1.2-1-0.4-1.9-0.8-2.9-1.1-1-0.4-1.9-0.8-2.9-1.1-1-0.4-2-0.7-3-1-2-0.6-4-1.3-6-1.9-2-0.6-4.1-1.1-6.1-1.7-2-0.6-4.1-1-6.2-1.4-2.1-0.4-4.2-0.9-6.3-1.2-2.1-0.3-4.2-0.7-6.4-1-2.1-0.2-4.3-0.5-6.5-0.7-17.3-1.7-35.2-1-53 1.9-2.2 0.3-4.5 0.8-6.7 1.2-2.2 0.4-4.5 0.8-6.7 1.4-2.2 0.5-4.4 1-6.7 1.5-2.2 0.6-4.4 1.2-6.6 1.7-8.8 2.4-17.7 5.2-26.4 8.5q-1.7 0.6-3.3 1.2c-1.1 0.4-2.2 0.9-3.3 1.3-2.2 0.9-4.4 1.7-6.5 2.6-4.3 1.9-8.6 3.7-12.9 5.7-4.3 2-8.5 4.1-12.8 6.2q-3.2 1.6-6.3 3.3c-1.1 0.6-2.1 1.1-3.2 1.7-1.1 0.6-2.1 1.2-3.1 1.7-2.1 1.2-4.2 2.3-6.3 3.5-2.1 1.2-4.1 2.4-6.2 3.7l-3.1 1.8-3.1 1.9c-2.1 1.3-4.1 2.5-6.2 3.8-4.1 2.6-8.2 5.2-12.2 8-2 1.4-4.1 2.7-6 4.1q-3 2.1-6 4.2c-2 1.4-4 2.9-6 4.3-2 1.5-4 2.9-5.9 4.4-3.9 3-7.8 6-11.7 9.1-7.8 6.1-15.4 12.5-22.9 19-15 13.1-29.5 26.9-43.4 41.4q-2.4 2.5-4.8 5.1z"/>
|
||||
<path id="<Path>" class="s0" d="m157.7 674.4c-2.1-0.6-4.2-1.3-6.4-1.9-1.1-0.3-2.2-0.6-3.3-1-1.1-0.4-2.2-0.8-3.4-1.2-2.3-0.8-4.6-1.6-7-2.4-2.3-0.9-4.7-1.8-7.2-2.8-1.2-0.5-2.5-0.9-3.7-1.5-1.2-0.5-2.5-1.1-3.7-1.6-1.2-0.5-2.5-1.1-3.8-1.6-0.6-0.3-1.3-0.6-1.9-0.8-0.6-0.3-1.3-0.6-1.9-0.9-2.5-1.2-5.1-2.5-7.8-3.8q-3.9-2.1-7.8-4.2c-10.5-5.9-21.2-13-31.5-21.7-10.3-8.7-20-19.1-28.5-31.1l-0.8-1.1-0.8-1.2c-0.5-0.8-1-1.6-1.5-2.3-0.5-0.8-1-1.6-1.5-2.3-0.3-0.4-0.5-0.8-0.8-1.2l-0.7-1.2c-0.9-1.6-1.9-3.3-2.8-4.9-0.5-0.8-0.9-1.7-1.3-2.5-0.4-0.8-0.9-1.7-1.3-2.5-0.4-0.9-0.9-1.7-1.3-2.6-0.5-0.8-0.8-1.7-1.2-2.6-0.8-1.8-1.6-3.5-2.4-5.3-0.7-1.8-1.4-3.6-2.1-5.5-0.3-0.9-0.7-1.8-1-2.8-0.3-0.9-0.6-1.9-0.9-2.8-0.3-0.9-0.6-1.9-0.9-2.8l-0.5-1.4c-0.2-0.5-0.3-1-0.4-1.4-0.5-1.9-1.1-3.9-1.6-5.8-0.3-1-0.5-2-0.7-2.9-0.2-1-0.4-2-0.7-3-0.2-1-0.4-2-0.7-3-0.2-1-0.4-2-0.5-3-1.6-8-2.4-16.3-3-24.7-1-16.8 0.1-34.2 2.7-51.7 0.4-2.2 0.7-4.4 1.1-6.6q0.6-3.3 1.2-6.6 0.3-1.7 0.6-3.3c0.2-1.1 0.5-2.2 0.7-3.3q0.8-3.3 1.5-6.6c1.1-4.4 2.2-8.8 3.5-13.2q0.5-1.7 0.9-3.3c0.3-1.1 0.6-2.2 1-3.3 0.7-2.2 1.4-4.4 2-6.6 0.7-2.2 1.5-4.4 2.2-6.6 0.7-2.2 1.5-4.4 2.3-6.6q1.2-3.3 2.4-6.6c0.8-2.2 1.7-4.4 2.5-6.5l1.3-3.3 1.4-3.3c0.9-2.2 1.8-4.3 2.7-6.5 1-2.2 1.9-4.3 2.9-6.5 0.5-1.1 0.9-2.2 1.4-3.2l1.5-3.2c3.3-7.2 6.8-14.2 10.5-21.3-1.5-6.2-2.9-12.4-4.2-18.7-1.5 2.9-3 5.8-4.4 8.7-4.3 8.8-8.6 17.6-12.4 26.6l-1.5 3.3c-0.5 1.1-0.9 2.2-1.4 3.4-0.9 2.2-1.8 4.5-2.8 6.7-0.9 2.3-1.7 4.5-2.6 6.7l-1.3 3.4-1.2 3.4c-0.8 2.3-1.6 4.5-2.4 6.8-0.8 2.3-1.5 4.5-2.3 6.8-0.8 2.3-1.5 4.5-2.2 6.8-0.7 2.3-1.4 4.5-2.1 6.8-0.6 2.3-1.3 4.5-1.9 6.8-0.3 1.1-0.6 2.3-0.9 3.4-0.3 1.1-0.6 2.3-0.9 3.4-1.2 4.5-2.2 9.1-3.2 13.6-0.5 2.3-0.9 4.6-1.4 6.8-0.2 1.1-0.4 2.3-0.7 3.4-0.2 1.1-0.4 2.3-0.6 3.4-0.4 2.3-0.8 4.5-1.1 6.8-0.3 2.3-0.6 4.5-0.9 6.8-2.3 18.1-2.9 36.1-1.4 53.4 0.8 8.7 1.9 17.2 3.7 25.5 0.2 1 0.4 2.1 0.6 3.1 0.3 1 0.5 2 0.8 3.1 0.3 1 0.5 2 0.8 3 0.3 1 0.5 2 0.8 3q0.9 3 1.8 6c0.2 0.5 0.3 1 0.5 1.5l0.5 1.5c0.3 1 0.7 1.9 1 2.9 0.3 1 0.7 1.9 1 2.9 0.3 1 0.8 1.9 1.1 2.8 0.8 1.9 1.6 3.7 2.3 5.6 0.9 1.8 1.7 3.6 2.6 5.4 0.4 0.9 0.8 1.8 1.3 2.7 0.5 0.9 0.9 1.7 1.4 2.6 0.5 0.9 0.9 1.7 1.4 2.6 0.5 0.9 0.9 1.7 1.5 2.6 1 1.7 2 3.3 3 5l0.8 1.2c0.3 0.4 0.5 0.8 0.8 1.2 0.5 0.8 1.1 1.6 1.6 2.4 0.5 0.8 1.1 1.6 1.6 2.4l0.8 1.2 0.9 1.1c9.1 12.2 19.4 22.6 30.1 31.2 10.8 8.6 21.8 15.6 32.6 21.3 2.7 1.4 5.4 2.7 8 4.1 2.7 1.2 5.3 2.4 7.9 3.6 0.6 0.3 1.3 0.6 1.9 0.9 0.7 0.3 1.3 0.5 2 0.8 1.3 0.5 2.6 1 3.9 1.6 1.3 0.5 2.5 1 3.8 1.5 1.2 0.5 2.5 0.9 3.8 1.4 2.5 0.9 4.9 1.8 7.3 2.6 2.4 0.8 4.8 1.5 7.1 2.2 1.2 0.4 2.3 0.7 3.4 1.1 1.1 0.4 2.3 0.6 3.4 1 2.2 0.6 4.4 1.2 6.5 1.8 16.9 4.5 30.4 6.7 39.7 8.3 9.3 1.3 14.2 2 14.2 2 0 0-4.9-0.8-14.1-2.4-9.1-2-22.5-4.6-39.3-9.4z"/>
|
||||
</g>
|
||||
<g id="<Group>">
|
||||
<g id="<Group>">
|
||||
</g>
|
||||
<g id="<Group>">
|
||||
<path id="<Path>" class="s1" d="m294.7 337.1v326.6c19.1 18.4 35.9 32.8 48.1 42.8 12.2-10 29-24.4 48.1-42.8v-326.6h199.8c8-30.4 13.7-62.5 16.3-96.1h-528.4c2.6 33.6 8.4 65.7 16.3 96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="<Group>">
|
||||
<path id="<Compound Path>" fill-rule="evenodd" class="s1" d="m342.8 778.5l-11-7.6c-3.2-2.2-79.3-55.6-156.2-152.6-45.3-57.1-81.4-117.4-107.3-179.4-32.5-77.8-49-158.4-49-239.5v-13.7l323.5-112.5 323.5 112.5v13.7c0 81.1-16.5 161.6-49 239.5-25.9 62-62 122.4-107.3 179.4-76.9 97-153 150.4-156.2 152.6zm-284.9-565.6c4.1 165.5 81.7 297.8 147.1 380.5 56.8 71.9 114.4 119.6 137.8 137.7 23.5-18.1 81-65.8 137.8-137.7 65.4-82.7 143-215 147.1-380.5l-284.9-99z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
3
assets/custom-icons/icons/uphold.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="sc-fd180167-0 jepyKs" color="n01" viewBox="0 0 500 500">
|
||||
<path fill="#6fe68a" d="M296.353 467.745c-14.174 5.154-29.636 6.443-45.099 6.443h-2.577c-15.463 0-30.925-2.578-45.1-7.732-7.73-2.577-14.173 2.577-16.75 9.02-2.577 7.731 2.577 15.463 9.02 16.751 16.75 5.154 34.79 7.731 52.83 7.731h2.577c18.04 0 36.08-2.577 52.83-7.73 6.443-2.578 10.309-10.31 7.732-18.04-2.577-3.866-9.02-7.732-15.463-6.443zm117.258-344.042s0-1.289 0 0C383.974 29.639 286.045-21.903 195.846 9.023c-51.541 18.039-92.775 60.561-109.526 114.68-15.463 46.388-10.308 108.238 15.463 166.222 33.502 78.602 94.063 134.01 148.182 134.01h1.289c54.119 0 114.68-55.408 148.183-134.01 24.482-57.984 29.636-119.834 14.174-166.222m-79.89-67.005c-27.06-1.288-56.696 11.597-85.044 34.791-28.348-23.194-57.985-36.08-85.044-34.79 51.542-37.368 119.835-37.368 170.088 0m-61.85 333.733c-14.174 6.443-30.925 6.443-45.1 0-46.387-21.905-55.407-110.815-20.616-191.993 10.308-25.771 24.482-48.965 42.522-68.293 18.04 20.616 32.214 43.81 42.522 68.293 36.08 82.467 27.06 170.088-19.328 191.993M124.976 278.328c-21.905-51.541-27.06-105.66-14.174-146.894 6.443-20.617 19.329-36.08 34.791-43.81 23.194-10.309 54.12-1.289 85.044 23.193-19.328 23.194-36.08 48.965-47.676 76.025-23.194 55.407-28.348 113.392-18.04 157.202-16.75-19.328-29.636-41.233-39.945-65.716m248.69 0c-10.309 23.194-23.194 45.1-38.657 65.716 10.309-43.81 5.155-101.795-18.04-157.202-11.596-28.348-28.347-54.12-47.675-76.025 29.636-24.482 60.561-33.502 85.044-21.905 15.462 7.731 27.06 21.905 34.79 43.81 11.597 39.946 6.443 94.065-15.462 145.606z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g style="mix-blend-mode:difference">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M250.207 5C111.849 5 0 117.52 0 256.723C0 367.996 71.6655 462.186 171.084 495.522C183.514 498.028 188.067 490.106 188.067 483.442C188.067 477.606 187.658 457.603 187.658 436.761C118.056 451.767 103.562 406.754 103.562 406.754C92.3769 377.581 75.8036 370.083 75.8036 370.083C53.0231 354.662 77.463 354.662 77.463 354.662C102.733 356.329 115.992 380.501 115.992 380.501C138.358 418.84 174.398 408.007 188.897 401.338C190.966 385.084 197.599 373.832 204.641 367.582C149.128 361.746 90.7226 340.075 90.7226 243.385C90.7226 215.879 100.658 193.374 116.402 175.872C113.918 169.622 105.217 143.779 118.891 109.189C118.891 109.189 140.017 102.519 187.653 135.028C208.047 129.517 229.079 126.714 250.207 126.691C271.333 126.691 292.869 129.611 312.756 135.028C360.396 102.519 381.523 109.189 381.523 109.189C395.197 143.779 386.491 169.622 384.007 175.872C400.165 193.374 409.691 215.879 409.691 243.385C409.691 340.075 351.285 361.326 295.358 367.582C304.474 375.499 312.341 390.5 312.341 414.257C312.341 448.013 311.931 475.105 311.931 483.437C311.931 490.106 316.49 498.028 328.914 495.527C428.333 462.18 499.999 367.996 499.999 256.723C500.409 117.52 388.15 5 250.207 5Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
25
distribute_options.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
output: dist/
|
||||
|
||||
releases:
|
||||
- name: dev
|
||||
jobs:
|
||||
- name: release-dev-linux-zip
|
||||
package:
|
||||
platform: linux
|
||||
target: zip
|
||||
- name: release-dev-linux-deb
|
||||
package:
|
||||
platform: linux
|
||||
target: deb
|
||||
- name: release-dev-linux-appimage
|
||||
package:
|
||||
platform: linux
|
||||
target: appimage
|
||||
- name: release-dev-windows-exe
|
||||
package:
|
||||
platform: windows
|
||||
target: exe
|
||||
- name: release-dev-macos-dmg
|
||||
package:
|
||||
platform: macos
|
||||
target: dmg
|
||||
2
flutter
253
ios/Podfile.lock
@@ -1,7 +1,9 @@
|
||||
PODS:
|
||||
- connectivity (0.0.1):
|
||||
- app_links (0.0.1):
|
||||
- Flutter
|
||||
- Reachability
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- DKImagePickerController/Core (4.3.4):
|
||||
@@ -45,88 +47,29 @@ PODS:
|
||||
- Flutter (1.0.0)
|
||||
- flutter_email_sender (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview (0.0.1):
|
||||
- flutter_inappwebview_ios (0.0.1):
|
||||
- Flutter
|
||||
- flutter_inappwebview/Core (= 0.0.1)
|
||||
- flutter_inappwebview_ios/Core (= 0.0.1)
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_inappwebview/Core (0.0.1):
|
||||
- flutter_inappwebview_ios/Core (0.0.1):
|
||||
- Flutter
|
||||
- OrderedSet (~> 5.0)
|
||||
- flutter_local_authentication (1.2.0):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- flutter_sodium (0.0.1):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- GoogleDataTransport (9.2.5):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleMLKit/BarcodeScanning (4.0.0):
|
||||
- GoogleMLKit/MLKitCore
|
||||
- MLKitBarcodeScanning (~> 3.0.0)
|
||||
- GoogleMLKit/MLKitCore (4.0.0):
|
||||
- MLKitCommon (~> 9.0.0)
|
||||
- GoogleToolboxForMac/DebugUtils (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (2.3.2)
|
||||
- GoogleToolboxForMac/Logger (2.3.2):
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSData+zlib (2.3.2)":
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.3.2)":
|
||||
- GoogleToolboxForMac/DebugUtils (= 2.3.2)
|
||||
- GoogleToolboxForMac/Defines (= 2.3.2)
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (= 2.3.2)"
|
||||
- "GoogleToolboxForMac/NSString+URLArguments (2.3.2)"
|
||||
- GoogleUtilities/Environment (7.11.5):
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.11.5):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/UserDefaults (7.11.5):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilitiesComponents (1.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GTMSessionFetcher/Core (2.3.0)
|
||||
- local_auth_ios (0.0.1):
|
||||
- Flutter
|
||||
- MLImage (1.0.0-beta4)
|
||||
- MLKitBarcodeScanning (3.0.0):
|
||||
- MLKitCommon (~> 9.0)
|
||||
- MLKitVision (~> 5.0)
|
||||
- MLKitCommon (9.0.0):
|
||||
- GoogleDataTransport (~> 9.0)
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
|
||||
- GoogleUtilities/UserDefaults (~> 7.0)
|
||||
- GoogleUtilitiesComponents (~> 1.0)
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
|
||||
- MLKitVision (5.0.0):
|
||||
- GoogleToolboxForMac/Logger (~> 2.1)
|
||||
- "GoogleToolboxForMac/NSData+zlib (~> 2.1)"
|
||||
- GTMSessionFetcher/Core (< 3.0, >= 1.1)
|
||||
- MLImage (= 1.0.0-beta4)
|
||||
- MLKitCommon (~> 9.0)
|
||||
- mobile_scanner (3.5.2):
|
||||
- Flutter
|
||||
- GoogleMLKit/BarcodeScanning (~> 4.0.0)
|
||||
- move_to_background (0.0.1):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- nanopb (2.30909.0):
|
||||
- nanopb/decode (= 2.30909.0)
|
||||
- nanopb/encode (= 2.30909.0)
|
||||
- nanopb/decode (2.30909.0)
|
||||
- nanopb/encode (2.30909.0)
|
||||
- open_filex (0.0.2):
|
||||
- Flutter
|
||||
- OrderedSet (5.0.0)
|
||||
@@ -135,101 +78,104 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- photo_manager (2.0.0):
|
||||
- privacy_screen (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.3.1)
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- Reachability (3.2)
|
||||
- SDWebImage (5.17.0):
|
||||
- SDWebImage/Core (= 5.17.0)
|
||||
- SDWebImage/Core (5.17.0)
|
||||
- Sentry/HybridSDK (8.9.1):
|
||||
- SentryPrivate (= 8.9.1)
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- SDWebImage (5.18.10):
|
||||
- SDWebImage/Core (= 5.18.10)
|
||||
- SDWebImage/Core (5.18.10)
|
||||
- Sentry/HybridSDK (8.19.0):
|
||||
- SentryPrivate (= 8.19.0)
|
||||
- sentry_flutter (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.9.1)
|
||||
- SentryPrivate (8.9.1)
|
||||
- Sentry/HybridSDK (= 8.19.0)
|
||||
- SentryPrivate (8.19.0)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- smart_auth (0.0.1):
|
||||
- Flutter
|
||||
- sodium_libs (2.2.0):
|
||||
- Flutter
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FMDB (>= 2.7.5)
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.0.0)
|
||||
- uni_links (0.0.1):
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.45.1):
|
||||
- sqlite3/common (= 3.45.1)
|
||||
- sqlite3/common (3.45.1)
|
||||
- sqlite3/fts5 (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.45.1):
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (~> 3.45.1)
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.1.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
|
||||
DEPENDENCIES:
|
||||
- connectivity (from `.symlinks/plugins/connectivity/ios`)
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- file_picker (from `.symlinks/plugins/file_picker/ios`)
|
||||
- file_saver (from `.symlinks/plugins/file_saver/ios`)
|
||||
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
|
||||
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
|
||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/ios`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- uni_links (from `.symlinks/plugins/uni_links/ios`)
|
||||
- smart_auth (from `.symlinks/plugins/smart_auth/ios`)
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- DKImagePickerController
|
||||
- DKPhotoGallery
|
||||
- FMDB
|
||||
- GoogleDataTransport
|
||||
- GoogleMLKit
|
||||
- GoogleToolboxForMac
|
||||
- GoogleUtilities
|
||||
- GoogleUtilitiesComponents
|
||||
- GTMSessionFetcher
|
||||
- MLImage
|
||||
- MLKitBarcodeScanning
|
||||
- MLKitCommon
|
||||
- MLKitVision
|
||||
- MTBBarcodeScanner
|
||||
- nanopb
|
||||
- OrderedSet
|
||||
- PromisesObjC
|
||||
- Reachability
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- Sentry
|
||||
- SentryPrivate
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
connectivity:
|
||||
:path: ".symlinks/plugins/connectivity/ios"
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
file_picker:
|
||||
@@ -242,22 +188,20 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter
|
||||
flutter_email_sender:
|
||||
:path: ".symlinks/plugins/flutter_email_sender/ios"
|
||||
flutter_inappwebview:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview/ios"
|
||||
flutter_inappwebview_ios:
|
||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||
flutter_local_authentication:
|
||||
:path: ".symlinks/plugins/flutter_local_authentication/ios"
|
||||
flutter_local_notifications:
|
||||
:path: ".symlinks/plugins/flutter_local_notifications/ios"
|
||||
flutter_native_splash:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
flutter_secure_storage:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
flutter_sodium:
|
||||
:path: ".symlinks/plugins/flutter_sodium/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
local_auth_ios:
|
||||
:path: ".symlinks/plugins/local_auth_ios/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/ios"
|
||||
move_to_background:
|
||||
:path: ".symlinks/plugins/move_to_background/ios"
|
||||
open_filex:
|
||||
@@ -266,8 +210,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/ios"
|
||||
privacy_screen:
|
||||
:path: ".symlinks/plugins/privacy_screen/ios"
|
||||
qr_code_scanner:
|
||||
:path: ".symlinks/plugins/qr_code_scanner/ios"
|
||||
sentry_flutter:
|
||||
@@ -276,68 +220,59 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
smart_auth:
|
||||
:path: ".symlinks/plugins/smart_auth/ios"
|
||||
sodium_libs:
|
||||
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
video_player_avfoundation:
|
||||
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
|
||||
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
|
||||
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
|
||||
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
|
||||
flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
GoogleDataTransport: 54dee9d48d14580407f8f5fbf2f496e92437a2f2
|
||||
GoogleMLKit: 2bd0dc6253c4d4f227aad460f69215a504b2980e
|
||||
GoogleToolboxForMac: 8bef7c7c5cf7291c687cf5354f39f9db6399ad34
|
||||
GoogleUtilities: 13e2c67ede716b8741c7989e26893d151b2b2084
|
||||
GoogleUtilitiesComponents: 679b2c881db3b615a2777504623df6122dd20afe
|
||||
GTMSessionFetcher: 3a63d75eecd6aa32c2fc79f578064e1214dfdec2
|
||||
local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
|
||||
MLImage: 7bb7c4264164ade9bf64f679b40fb29c8f33ee9b
|
||||
MLKitBarcodeScanning: 04e264482c5f3810cb89ebc134ef6b61e67db505
|
||||
MLKitCommon: c1b791c3e667091918d91bda4bba69a91011e390
|
||||
MLKitVision: 8baa5f46ee3352614169b85250574fde38c36f49
|
||||
mobile_scanner: 5090a13b7a35fc1c25b0d97e18e84f271a6eb605
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||
PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
|
||||
SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
|
||||
Sentry: e3203780941722a1fcfee99e351de14244c7f806
|
||||
sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
|
||||
SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: fc8f2d48bbfd72ef39d70e981bd24a3f3be53fec
|
||||
Sentry: 1ebcaef678a27c8ac515f974cb5425dd1bbdec2f
|
||||
sentry_flutter: ecdfbedee55337205561cfa782ee02d31ec83e1f
|
||||
SentryPrivate: 765c9b4ebe9ac1a5fcdc067c5a1cfbf3f10e1677
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
||||
smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
|
||||
sodium_libs: 0486eb2c3172ce494406367d4b379042444b769d
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
|
||||
sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
video_player_avfoundation: 8563f13d8fc8b2c29dc2d09e60b660e4e8128837
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||
|
||||
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
|
||||
|
||||
COCOAPODS: 1.12.1
|
||||
COCOAPODS: 1.14.3
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import UIKit
|
||||
import app_links
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@@ -8,6 +9,15 @@ import Flutter
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
|
||||
AppLinks.shared.handleLink(url: url)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
// return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,5 +77,9 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
class App extends StatefulWidget {
|
||||
final Locale locale;
|
||||
const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
|
||||
const App({super.key, this.locale = const Locale("en")});
|
||||
|
||||
static void setLocale(BuildContext context, Locale newLocale) {
|
||||
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
||||
|
||||
@@ -12,12 +12,12 @@ import 'package:ente_auth/models/key_attributes.dart';
|
||||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration {
|
||||
@@ -70,9 +70,10 @@ class Configuration {
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage();
|
||||
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
||||
_tempDirectory = _documentsDirectory + "/temp/";
|
||||
_tempDirectory = "$_documentsDirectory/temp/";
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
@@ -160,7 +161,7 @@ class Configuration {
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
@@ -170,28 +171,28 @@ class Configuration {
|
||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = await CryptoUtil.generateKeyPair();
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.sk, masterKey);
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
Sodium.bin2base64(kekSalt),
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
Sodium.bin2base64(keyPair.pk),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(kekSalt),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
derivedKeyResult.memLimit,
|
||||
derivedKeyResult.opsLimit,
|
||||
Sodium.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedMasterKey.nonce!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
Sodium.bin2base64(masterKey),
|
||||
Sodium.bin2hex(recoveryKey),
|
||||
Sodium.bin2base64(keyPair.sk),
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
@@ -206,7 +207,7 @@ class Configuration {
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
@@ -218,9 +219,9 @@ class Configuration {
|
||||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: Sodium.bin2base64(kekSalt),
|
||||
encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
@@ -238,8 +239,8 @@ class Configuration {
|
||||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
@@ -248,31 +249,31 @@ class Configuration {
|
||||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe('master-key failed, incorrect password?', e);
|
||||
throw Exception("Incorrect password");
|
||||
}
|
||||
_logger.info("master-key done");
|
||||
await setKey(Sodium.bin2base64(key));
|
||||
await setKey(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
@@ -291,28 +292,28 @@ class Configuration {
|
||||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
Sodium.hex2bin(recoveryKey),
|
||||
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(Sodium.bin2base64(masterKey));
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -400,27 +401,31 @@ class Configuration {
|
||||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : Sodium.base642bin(_key!);
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_authSecretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getOfflineSecretKey() {
|
||||
return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
|
||||
return _offlineAuthKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -447,7 +452,7 @@ class Configuration {
|
||||
iOptions: _secureStorageOptionsIOS,
|
||||
);
|
||||
} else {
|
||||
_offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
|
||||
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
|
||||
await _secureStorage.write(
|
||||
key: offlineAuthSecretKey,
|
||||
value: _offlineAuthKey,
|
||||
@@ -469,10 +474,14 @@ class Configuration {
|
||||
return _preferences.setBool(keyShouldShowLockScreen, value);
|
||||
}
|
||||
|
||||
void setVolatilePassword(String? volatilePassword) {
|
||||
void setVolatilePassword(String volatilePassword) {
|
||||
_volatilePassword = volatilePassword;
|
||||
}
|
||||
|
||||
void resetVolatilePassword() {
|
||||
_volatilePassword = null;
|
||||
}
|
||||
|
||||
String? getVolatilePassword() {
|
||||
return _volatilePassword;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
class InvalidFileError extends ArgumentError {
|
||||
InvalidFileError(String message) : super(message);
|
||||
InvalidFileError(String super.message);
|
||||
}
|
||||
|
||||
class InvalidFileUploadState extends AssertionError {
|
||||
InvalidFileUploadState(String message) : super(message);
|
||||
InvalidFileUploadState(String super.message);
|
||||
}
|
||||
|
||||
class SubscriptionAlreadyClaimedError extends Error {}
|
||||
@@ -30,17 +30,15 @@ class UnauthorizedError extends Error {}
|
||||
class RequestCancelledError extends Error {}
|
||||
|
||||
class InvalidSyncStatusError extends AssertionError {
|
||||
InvalidSyncStatusError(String message) : super(message);
|
||||
InvalidSyncStatusError(String super.message);
|
||||
}
|
||||
|
||||
class UnauthorizedEditError extends AssertionError {}
|
||||
|
||||
class InvalidStateError extends AssertionError {
|
||||
InvalidStateError(String message) : super(message);
|
||||
InvalidStateError(String super.message);
|
||||
}
|
||||
|
||||
class KeyDerivationError extends Error {}
|
||||
|
||||
class SrpSetupNotCompleteError extends Error {}
|
||||
|
||||
class AuthenticatorKeyNotFound extends Error {}
|
||||
|
||||
@@ -235,14 +235,14 @@ class SuperLogging {
|
||||
extraLines = null;
|
||||
}
|
||||
|
||||
final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
|
||||
final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
|
||||
|
||||
// write to stdout
|
||||
printLog(str);
|
||||
|
||||
// push to log queue
|
||||
if (fileIsEnabled) {
|
||||
fileQueueEntries.add(str + '\n');
|
||||
fileQueueEntries.add('$str\n');
|
||||
if (fileQueueEntries.length == 1) {
|
||||
flushQueue();
|
||||
}
|
||||
@@ -275,7 +275,7 @@ class SuperLogging {
|
||||
static var logChunkSize = 800;
|
||||
|
||||
static void printLog(String text) {
|
||||
text.chunked(logChunkSize).forEach(print);
|
||||
text.chunked(logChunkSize).forEach(debugPrint);
|
||||
}
|
||||
|
||||
/// A queue to be consumed by [setupSentry].
|
||||
@@ -354,7 +354,7 @@ class SuperLogging {
|
||||
final date = config.dateFmt!.parse(basename(file.path));
|
||||
dates[file as File] = date;
|
||||
files.add(file);
|
||||
} on FormatException {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
final nowTime = DateTime.now();
|
||||
|
||||
@@ -374,7 +374,7 @@ class SuperLogging {
|
||||
"deleting log file ${file.path}",
|
||||
);
|
||||
await file.delete();
|
||||
} catch (ignore) {}
|
||||
} on Exception catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ class TunneledTransport implements Transport {
|
||||
_credentialBuilder = _CredentialBuilder(
|
||||
_dsn,
|
||||
_options.sentryClientName,
|
||||
// ignore: invalid_use_of_internal_member
|
||||
_options.clock,
|
||||
);
|
||||
}
|
||||
@@ -45,7 +46,7 @@ class TunneledTransport implements Transport {
|
||||
_options.logger(
|
||||
SentryLevel.error,
|
||||
'API returned an error, statusCode = ${response.statusCode}, '
|
||||
'body = ${response.body}',
|
||||
'body = ${response.body}',
|
||||
);
|
||||
}
|
||||
return const SentryId.empty();
|
||||
@@ -64,8 +65,8 @@ class TunneledTransport implements Transport {
|
||||
}
|
||||
|
||||
Future<StreamedRequest> _createStreamedRequest(
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
SentryEnvelope envelope,
|
||||
) async {
|
||||
final streamedRequest = StreamedRequest('POST', _tunnel);
|
||||
envelope
|
||||
.envelopeStream(_options)
|
||||
@@ -90,10 +91,10 @@ class _CredentialBuilder {
|
||||
_clock = clock;
|
||||
|
||||
factory _CredentialBuilder(
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
Dsn? dsn,
|
||||
String sdkIdentifier,
|
||||
ClockProvider clock,
|
||||
) {
|
||||
final authHeader = _buildAuthHeader(
|
||||
publicKey: dsn?.publicKey,
|
||||
secretKey: dsn?.secretKey,
|
||||
|
||||
@@ -3,9 +3,10 @@ import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/utils/package_info_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
@@ -21,16 +22,22 @@ class Network {
|
||||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||
? FkUserAgent.userAgent
|
||||
: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -38,11 +45,14 @@ class Network {
|
||||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiEndpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
if (PlatformUtil.isMobile())
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||
else
|
||||
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -76,8 +86,8 @@ class EnteRequestInterceptor extends Interceptor {
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
options.baseUrl == enteEndpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
options.baseUrl == enteEndpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
);
|
||||
}
|
||||
// ignore: prefer_const_constructors
|
||||
|
||||
@@ -11,6 +11,7 @@ final lightThemeData = ThemeData(
|
||||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
primaryIconTheme:
|
||||
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
||||
buttonTheme: const ButtonThemeData(),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
||||
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
|
||||
@@ -72,24 +73,42 @@ final lightThemeData = ThemeData(
|
||||
? const Color.fromRGBO(255, 255, 255, 1)
|
||||
: const Color.fromRGBO(0, 0, 0, 1);
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: 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;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: 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;
|
||||
}),
|
||||
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(
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
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;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
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;
|
||||
}),
|
||||
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,
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
||||
@@ -105,6 +124,7 @@ final darkThemeData = ThemeData(
|
||||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
buttonTheme: const ButtonThemeData().copyWith(
|
||||
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
||||
height: 56,
|
||||
),
|
||||
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
@@ -164,24 +184,43 @@ final darkThemeData = ThemeData(
|
||||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
}
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: 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;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: 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;
|
||||
}),
|
||||
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)),
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
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;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
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;
|
||||
}),
|
||||
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) {
|
||||
@@ -400,6 +439,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
fixedSize: const Size.fromHeight(56),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
||||
textStyle: const TextStyle(
|
||||
@@ -436,7 +476,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
||||
}) {
|
||||
return ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
|
||||
foregroundColor: onPrimary,
|
||||
backgroundColor: primary,
|
||||
elevation: elevation,
|
||||
alignment: Alignment.center,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@@ -10,12 +10,12 @@ class AuthenticatorGateway {
|
||||
late String _basedEndpoint;
|
||||
|
||||
AuthenticatorGateway(this._dio, this._config) {
|
||||
_basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
|
||||
_basedEndpoint = "${_config.getHttpEndpoint()}/authenticator";
|
||||
}
|
||||
|
||||
Future<void> createKey(String encKey, String header) async {
|
||||
await _dio.post(
|
||||
_basedEndpoint + "/key",
|
||||
"$_basedEndpoint/key",
|
||||
data: {
|
||||
"encryptedKey": encKey,
|
||||
"header": header,
|
||||
@@ -31,7 +31,7 @@ class AuthenticatorGateway {
|
||||
Future<AuthKey> getKey() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/key",
|
||||
"$_basedEndpoint/key",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -39,7 +39,7 @@ class AuthenticatorGateway {
|
||||
),
|
||||
);
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
throw AuthenticatorKeyNotFound();
|
||||
} else {
|
||||
@@ -52,7 +52,7 @@ class AuthenticatorGateway {
|
||||
|
||||
Future<AuthEntity> createEntity(String encryptedData, String header) async {
|
||||
final response = await _dio.post(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
data: {
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
@@ -72,7 +72,7 @@ class AuthenticatorGateway {
|
||||
String header,
|
||||
) async {
|
||||
await _dio.put(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
data: {
|
||||
"id": id,
|
||||
"encryptedData": encryptedData,
|
||||
@@ -90,7 +90,7 @@ class AuthenticatorGateway {
|
||||
String id,
|
||||
) async {
|
||||
await _dio.delete(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
queryParameters: {
|
||||
"id": id,
|
||||
},
|
||||
@@ -105,7 +105,7 @@ class AuthenticatorGateway {
|
||||
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/entity/diff",
|
||||
"$_basedEndpoint/entity/diff",
|
||||
queryParameters: {
|
||||
"sinceTime": sinceTime,
|
||||
"limit": limit,
|
||||
@@ -124,7 +124,7 @@ class AuthenticatorGateway {
|
||||
}
|
||||
return authEntities;
|
||||
} catch (e) {
|
||||
if (e is DioError && e.response?.statusCode == 401) {
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
throw UnauthorizedError();
|
||||
} else {
|
||||
rethrow;
|
||||
|
||||
1
lib/l10n/arb/app_ar.arb
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Konto",
|
||||
"unlock": "Entsperren",
|
||||
"recoveryKey": "Wiederherstellungsschlüssel",
|
||||
"counterAppBarTitle": "Zähler",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -60,6 +61,7 @@
|
||||
"contactSupport": "Support kontaktieren",
|
||||
"rateUsOnStore": "Bewerte uns auf {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Passwort überprüfen",
|
||||
"pleaseWait": "Bitte warten...",
|
||||
"generatingEncryptionKeysTitle": "Generierung von Verschlüsselungsschlüsseln...",
|
||||
@@ -75,12 +77,14 @@
|
||||
"changePassword": "Passwort ändern",
|
||||
"data": "Datei",
|
||||
"importCodes": "Codes importieren",
|
||||
"importTypePlainText": "Klartext",
|
||||
"importTypeEnteEncrypted": "ente verschlüsselt exportieren",
|
||||
"passwordForDecryptingExport": "Passwort um den Export zu entschlüsseln",
|
||||
"passwordEmptyError": "Passwort kann nicht leer sein",
|
||||
"importFromApp": "Importiere Codes von {appName}",
|
||||
"importGoogleAuthGuide": "Exportiere deine Accounts von Google Authenticator zu einem QR-Code, durch die \"Konten übertragen\" Option. Scanne den QR-Code danach mit einem anderen Gerät.\n\nTipp: Du kannst die Kamera eines Laptops verwenden, um ein Foto den dem QR-Code zu erstellen.",
|
||||
"importSelectJsonFile": "Wähle eine JSON-Datei",
|
||||
"importSelectAppExport": "{appName} Exportdatei auswählen",
|
||||
"importEnteEncGuide": "Wähle die von ente exportierte, verschlüsselte JSON-Datei",
|
||||
"exportCodes": "Codes exportieren",
|
||||
"importLabel": "Importieren",
|
||||
@@ -92,6 +96,7 @@
|
||||
"authToViewYourRecoveryKey": "Bitte authentifizieren um ihren Wiederherstellungscode anzuzeigen",
|
||||
"authToChangeYourEmail": "Bitte authentifizieren um ihre Emailadresse zu ändern",
|
||||
"authToChangeYourPassword": "Bitte authentifizieren um ihr Passwort zu ändern",
|
||||
"authToViewSecrets": "Bitte authentifizieren Sie sich, um ihren Wiederherstellungscode anzuzeigen",
|
||||
"ok": "Ok",
|
||||
"cancel": "Abbrechen",
|
||||
"yes": "Ja",
|
||||
@@ -177,6 +182,7 @@
|
||||
"enterDetailsManually": "Details manuell hinzufügen",
|
||||
"edit": "Editieren",
|
||||
"copiedToClipboard": "In die Zwischenablage kopieren",
|
||||
"copiedNextToClipboard": "Nächster Code wurde in die Zwischenablage kopiert",
|
||||
"error": "Fehler",
|
||||
"recoveryKeyCopiedToClipboard": "Wiederherstellungsschlüssel in die Zwischenablage kopiert",
|
||||
"recoveryKeyOnForgotPassword": "Sollten sie ihr Passwort vergessen, dann ist dieser Schlüssel die einzige Möglichkeit ihre Daten wiederherzustellen.",
|
||||
@@ -309,7 +315,68 @@
|
||||
"incorrectRecoveryKey": "Falscher Wiederherstellungs-Schlüssel",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "Der eingegebene Wiederherstellungs-Schlüssel ist ungültig",
|
||||
"enterPassword": "Passwort eingeben",
|
||||
"selectExportFormat": "Exportformat auswählen",
|
||||
"encrypted": "Verschlüsselt",
|
||||
"plainText": "Klartext",
|
||||
"passwordToEncryptExport": "Passwort zum Verschlüssen des Exports",
|
||||
"export": "Export",
|
||||
"useOffline": "Ohne Backup verwenden",
|
||||
"signInToBackup": "Melde dich an, um deine Codes zu sichern",
|
||||
"singIn": "Anmelden",
|
||||
"showLargeIcons": "Große Symbole anzeigen",
|
||||
"shouldHideCode": "Codes ausblenden",
|
||||
"doubleTapToViewHiddenCode": "Sie können auf einen Eintrag doppelt tippen, um den Code anzuzeigen",
|
||||
"minimizeAppOnCopy": "Beim Kopieren App minimieren",
|
||||
"editCodeAuthMessage": "Authentifizieren, um Code zu bearbeiten",
|
||||
"deleteCodeAuthMessage": "Authentifizieren, um Code zu löschen",
|
||||
"showQRAuthMessage": "Authentifizieren, um QR-Code anzuzeigen"
|
||||
"showQRAuthMessage": "Authentifizieren, um QR-Code anzuzeigen",
|
||||
"confirmAccountDeleteTitle": "Kontolöschung bestätigen",
|
||||
"androidBiometricHint": "Identität bestätigen",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Nicht erkannt. Versuchen Sie es erneut.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Erfolgreich",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Abbrechen",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Authentifizierung erforderlich",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biometrie erforderlich",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Geräteanmeldeinformationen erforderlich",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Geräteanmeldeinformationen erforderlich",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Zu den Einstellungen",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "Keine Internetverbindung",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
|
||||
"signOutFromOtherDevices": "Von anderen Geräten abmelden",
|
||||
"signOutOtherBody": "Falls du denkst, dass jemand dein Passwort kennen könnte, kannst du alle anderen Geräte von deinem Account abmelden.",
|
||||
"signOutOtherDevices": "Andere Geräte abmelden",
|
||||
"doNotSignOut": "Nicht abmelden",
|
||||
"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!"
|
||||
}
|
||||
@@ -59,7 +59,7 @@
|
||||
}
|
||||
},
|
||||
"contactSupport": "Contact support",
|
||||
"rateUsOnStore" : "Rate us on {storeName}",
|
||||
"rateUsOnStore": "Rate us on {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Verify password",
|
||||
@@ -84,22 +84,26 @@
|
||||
"importFromApp": "Import codes from {appName}",
|
||||
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
|
||||
"importSelectJsonFile": "Select JSON file",
|
||||
"importSelectAppExport": "Select {appName} export file",
|
||||
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
|
||||
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
|
||||
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
|
||||
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
|
||||
"import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup",
|
||||
"importLastpassGuide": "Use the \"Transfer accounts\" option within Lastpass Authenticator Settings and press \"Export accounts to file\". Import the JSON downloaded.",
|
||||
"exportCodes": "Export codes",
|
||||
"importLabel": "Import",
|
||||
"importInstruction": "Please select a file that contains a list of your codes in the following format",
|
||||
"importCodeDelimiterInfo": "The codes can be separated by a comma or a new line",
|
||||
"selectFile": "Select file",
|
||||
"emailVerificationToggle": "Email verification",
|
||||
"emailVerificationEnableWarning": "If you are storing the 2FA to your email with us, turning on email verification could result in a deadlock. If you are locked out of one service, you might not be able to log in to the other.",
|
||||
"emailVerificationEnableWarning": "To avoid getting locked out of your account, be sure to store a copy of your email 2FA outside of Ente Auth before enabling email verification.",
|
||||
"authToChangeEmailVerificationSetting": "Please authenticate to change email verification",
|
||||
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
|
||||
"authToChangeYourEmail": "Please authenticate to change your email",
|
||||
"authToChangeYourPassword": "Please authenticate to change your password",
|
||||
"authToViewSecrets": "Please authenticate to view your secrets",
|
||||
"authToInitiateSignIn": "Please authenticate to initiate sign in for backup.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancel",
|
||||
"yes": "Yes",
|
||||
@@ -193,6 +197,10 @@
|
||||
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
||||
"doThisLater": "Do this later",
|
||||
"saveKey": "Save key",
|
||||
"save": "Save",
|
||||
"send": "Send",
|
||||
"saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?",
|
||||
"saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?",
|
||||
"back": "Back",
|
||||
"createAccount": "Create account",
|
||||
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
||||
@@ -332,9 +340,10 @@
|
||||
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
|
||||
"showLargeIcons": "Show large icons",
|
||||
"shouldHideCode": "Hide codes",
|
||||
"doubleTapToViewHiddenCode": "You can double tap on an entry to view code",
|
||||
"focusOnSearchBar": "Focus search on app start",
|
||||
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
|
||||
"minimizeAppOnCopy": "Minimize app on copy",
|
||||
"minimizeAppOnCopy": "Minimize app on copy",
|
||||
"editCodeAuthMessage": "Authenticate to edit code",
|
||||
"deleteCodeAuthMessage": "Authenticate to delete code",
|
||||
"showQRAuthMessage": "Authenticate to show QR code",
|
||||
@@ -391,5 +400,14 @@
|
||||
"iOSOkButton": "OK",
|
||||
"@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."
|
||||
}
|
||||
}
|
||||
},
|
||||
"noInternetConnection": "No internet connection",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.",
|
||||
"signOutFromOtherDevices": "Sign out from other devices",
|
||||
"signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.",
|
||||
"signOutOtherDevices": "Sign out other devices",
|
||||
"doNotSignOut": "Do not sign out",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
|
||||
"recoveryKeySaved": "Recovery key saved in Downloads folder!"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Account",
|
||||
"unlock": "Sblocca",
|
||||
"recoveryKey": "Chiave di recupero",
|
||||
"counterAppBarTitle": "Contatore",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -83,9 +84,12 @@
|
||||
"importFromApp": "Importa codici da {appName}",
|
||||
"importGoogleAuthGuide": "Esporta i tuoi account da Google Authenticator in un codice QR utilizzando l'opzione \"Trasferisci Account\". Quindi, usando un altro dispositivo, scansiona il codice QR.\n\nSuggerimento: Puoi usare la webcam del tuo computer portatile per scattare una foto del codice QR.",
|
||||
"importSelectJsonFile": "Seleziona file JSON",
|
||||
"importSelectAppExport": "Seleziona il file di esportazione {appName}",
|
||||
"importEnteEncGuide": "Seleziona il file JSON criptato esportato da ente",
|
||||
"importRaivoGuide": "Utilizza l'opzione \"Esporta i codici OTP in archivio Zip\" nelle impostazioni di Raivo.\n\nEstrai il file zip e importa il file JSON.",
|
||||
"importBitwardenGuide": "Utilizzare l'opzione \"Esporta vault\" all'interno di Bitwarden Tools e importa il file JSON non crittografato.",
|
||||
"importAegisGuide": "Usa l'opzione \"Esporta la cassaforte\" nelle impostazioni di Aegis.\n\nSe la tua cassaforte è criptata, dovrai inserire la password della cassaforte per decriptarla.",
|
||||
"import2FasGuide": "Utilizza l'opzione \"Impostazioni->Backup -Export\" in 2FAS.\n\nSe il backup è crittografato, è necessario inserire la password per decriptare il backup",
|
||||
"exportCodes": "Esporta codici",
|
||||
"importLabel": "Importa",
|
||||
"importInstruction": "Per favore seleziona un file contenente una lista dei tuoi codici nel seguente formato",
|
||||
@@ -97,6 +101,8 @@
|
||||
"authToViewYourRecoveryKey": "Autenticati per visualizzare la tua chiave di recupero",
|
||||
"authToChangeYourEmail": "Autenticati per cambiare la tua email",
|
||||
"authToChangeYourPassword": "Autenticati per cambiare la tua password",
|
||||
"authToViewSecrets": "Autenticati per visualizzare i tuoi segreti",
|
||||
"authToInitiateSignIn": "Autenticazione per avviare l'accesso per il backup.",
|
||||
"ok": "OK",
|
||||
"cancel": "Annulla",
|
||||
"yes": "Si",
|
||||
@@ -329,6 +335,7 @@
|
||||
"offlineModeWarning": "Hai scelto di procedere senza backup. Si prega di eseguire backup manuali per assicurarsi che i codici siano al sicuro.",
|
||||
"showLargeIcons": "Mostra icone grandi",
|
||||
"shouldHideCode": "Nascondi i codici",
|
||||
"doubleTapToViewHiddenCode": "Puoi toccare due volte una voce per visualizzare il codice",
|
||||
"focusOnSearchBar": "Apri ricerca all'avvio dell'app",
|
||||
"confirmUpdatingkey": "Sei sicuro di voler aggiornare la chiave segreta?",
|
||||
"minimizeAppOnCopy": "Riduci a icona l'app dopo la copia",
|
||||
@@ -336,5 +343,65 @@
|
||||
"deleteCodeAuthMessage": "Autenticarsi per cancellare il codice",
|
||||
"showQRAuthMessage": "Autenticarsi per mostrare il codice QR",
|
||||
"confirmAccountDeleteTitle": "Conferma l'eliminazione dell'account",
|
||||
"confirmAccountDeleteMessage": "Questo account è collegato ad altre app di ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente."
|
||||
"confirmAccountDeleteMessage": "Questo account è collegato ad altre app di ente, se ne utilizzi.\n\nI tuoi dati caricati, su tutte le app di ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente.",
|
||||
"androidBiometricHint": "Verifica l'identità",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Non riconosciuto. Riprova.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Successo",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Annulla",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Autenticazione necessaria",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Autenticazione biometrica richiesta",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Credenziali del dispositivo richieste",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Credenziali del dispositivo richieste",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Vai alle impostazioni",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "L'autenticazione biometrica non è impostata sul tuo dispositivo. Vai a 'Impostazioni > Sicurezza' per impostarla.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "L'autenticazione biometrica è disabilitata. Blocca e sblocca lo schermo per abilitarla.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "L'autenticazione biometrica non è impostata sul tuo dispositivo. Abilita Touch ID o Face ID sul tuo telefono.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "Nessuna connessione internet",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Si prega di verificare la propria connessione Internet e riprovare.",
|
||||
"signOutFromOtherDevices": "Esci dagli altri dispositivi",
|
||||
"signOutOtherBody": "Se pensi che qualcuno possa conoscere la tua password, puoi forzare tutti gli altri dispositivi che usano il tuo account ad uscire.",
|
||||
"signOutOtherDevices": "Esci dagli altri dispositivi",
|
||||
"doNotSignOut": "Non uscire",
|
||||
"hearUsWhereTitle": "Dove hai sentito parlare di Ente? (opzionale)",
|
||||
"hearUsExplanation": "Non teniamo traccia delle installazioni dell'app. Sarebbe utile se ci dicessi dove ci hai trovato!"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "アカウント",
|
||||
"unlock": "ロック解除",
|
||||
"recoveryKey": "回復キー",
|
||||
"counterAppBarTitle": "カウンター",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -83,19 +84,26 @@
|
||||
"importFromApp": "{appName} からコードをインポート",
|
||||
"importGoogleAuthGuide": "Google Authenticator の \"アカウントを転送\" オプションを使用してあなたのアカウントを QR コードにエクスポートしてください。その後、他のデバイスで QR コードを読み取ってください。\n\nヒント: ノートパソコンのウェブカメラを使用して QR コードを撮影することができます。",
|
||||
"importSelectJsonFile": "JSON ファイルを選択",
|
||||
"importSelectAppExport": "{appName} のエクスポートファイルを選択",
|
||||
"importEnteEncGuide": "ente からエクスポートされた暗号化 JSON ファイルを選択",
|
||||
"importRaivoGuide": "Raivo の設定の \"OTP を zip アーカイブにエクスポート\" を使用してください。\n\nzip ファイルを解凍し JSON ファイルをインポートしてください。",
|
||||
"importBitwardenGuide": "Bitwarden Tools の \"保管庫のエクスポート\" オプションを使用後、平文の JSON ファイルをインポートしてください。",
|
||||
"importAegisGuide": "Aegis の設定の \"保管庫をエクスポート\" を使用してください。\n\n保管庫が暗号化されている場合、保管庫を復号するためにパスワードの入力が必要になります。",
|
||||
"import2FasGuide": "2FAS の \"設定->バックアップ-エクスポート\" オプションを使用します。\n\nバックアップが暗号化されている場合は、バックアップを復号するためにパスワードの入力が必要になります。",
|
||||
"importLastpassGuide": "Lastpass Authenticator の設定内にある \"アカウントを転送\" オプションを使用し、\"アカウントをファイルにエクスポート\" を押してください。その後ダウンロードした JSON をインポートしてください。",
|
||||
"exportCodes": "コードをエクスポート",
|
||||
"importLabel": "インポート",
|
||||
"importInstruction": "以下の形式のコードのリストを含むファイルを選択してください",
|
||||
"importCodeDelimiterInfo": "コードはカンマまたは改行で区切ることができます",
|
||||
"selectFile": "ファイルを選択",
|
||||
"emailVerificationToggle": "メール認証",
|
||||
"emailVerificationEnableWarning": "アカウントから閉め出されるのを防ぐために、メール認証を有効にする前にメール 2FA のコピーが ente Auth 外に保管されていることを確認してください。",
|
||||
"authToChangeEmailVerificationSetting": "メール認証を変更するためには認証が必要です",
|
||||
"authToViewYourRecoveryKey": "回復キーを表示するためには認証が必要です",
|
||||
"authToChangeYourEmail": "メールアドレスを変更するためには認証が必要です",
|
||||
"authToChangeYourPassword": "パスワードを変更するためには認証が必要です",
|
||||
"authToViewSecrets": "秘密鍵を閲覧するためには認証が必要です",
|
||||
"authToInitiateSignIn": "バックアップのためのサインインを開始するためには認証が必要です。",
|
||||
"ok": "OK",
|
||||
"cancel": "キャンセル",
|
||||
"yes": "はい",
|
||||
@@ -118,19 +126,25 @@
|
||||
"faq_a_1": "ente でバックアップされたすべてのコードはエンドツーエンドで暗号化されて保管されます。これはあなただけがコードにアクセスできることを意味します。私たちのアプリはオープンソースであり、私たちの暗号は外部有識者によって検証済みです。",
|
||||
"faq_q_2": "パソコンから私のコードにアクセスできますか?",
|
||||
"faq_a_2": "auth.ente.io で Web からコードにアクセス可能です。",
|
||||
"faq_q_3": "コードを削除するにはどうすればいいですか?",
|
||||
"faq_a_3": "その項目を左にスワイプすることでコードを削除できます。",
|
||||
"faq_q_4": "このプロジェクトを支援するにはどうすればいいですか?",
|
||||
"faq_a_4": "ente.io で私たちの写真アプリを購読することでこのプロジェクトの開発を支援できます。",
|
||||
"faq_q_5": "ente Auth で FaceID ロックを有効にするにはどうすればいいですか?",
|
||||
"faq_a_5": "設定→セキュリティ→画面のロックから FaceID ロックを有効にできます。",
|
||||
"somethingWentWrongMessage": "問題が発生しました、再試行してください",
|
||||
"leaveFamily": "ファミリーから退会",
|
||||
"leaveFamilyMessage": "本当にファミリープランを退会しますか?",
|
||||
"inFamilyPlanMessage": "ファミリープランに入会しています!",
|
||||
"leaveFamily": "ファミリープランから退会",
|
||||
"leaveFamilyMessage": "本当にファミリープランを退会しますか?",
|
||||
"inFamilyPlanMessage": "ファミリープランに入会しています!",
|
||||
"swipeHint": "左にスワイプしてコードを編集、削除します",
|
||||
"scan": "スキャン",
|
||||
"scanACode": "コードをスキャン",
|
||||
"scan": "読み取り",
|
||||
"scanACode": "コードを読み取り",
|
||||
"verify": "認証",
|
||||
"verifyEmail": "メールアドレス認証",
|
||||
"enterCodeHint": "認証アプリに表示された6ケタのコードを入力してください",
|
||||
"lostDeviceTitle": "デバイスを紛失しましたか?",
|
||||
"twoFactorAuthTitle": "2要素認証",
|
||||
"recoverAccount": "アカウント回復",
|
||||
"enterCodeHint": "認証アプリに表示された 6 桁のコードを入力してください",
|
||||
"lostDeviceTitle": "デバイスを紛失しましたか?",
|
||||
"twoFactorAuthTitle": "2 要素認証",
|
||||
"recoverAccount": "アカウントを回復",
|
||||
"enterRecoveryKeyHint": "回復キーを入力",
|
||||
"recover": "回復",
|
||||
"contactSupportViaEmailMessage": "登録したメールアドレスから {email} に問い合わせメールをお送りください",
|
||||
@@ -141,15 +155,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"noRecoveryKeyTitle": "回復キーがありませんか?",
|
||||
"noRecoveryKeyTitle": "回復キーがありませんか?",
|
||||
"enterEmailHint": "メールアドレスを入力してください",
|
||||
"invalidEmailTitle": "メールアドレスが無効です",
|
||||
"invalidEmailMessage": "有効なメール アドレスを入力して下さい",
|
||||
"invalidEmailMessage": "有効なメールアドレスを入力して下さい",
|
||||
"deleteAccount": "アカウント削除",
|
||||
"deleteAccountQuery": "ご不便をおかけして申し訳ありません。なにか問題が発生していますか?",
|
||||
"deleteAccountQuery": "ご不便をおかけして申し訳ありません。なにか問題が発生していますか?",
|
||||
"yesSendFeedbackAction": "はい、フィードバックを送信します",
|
||||
"noDeleteAccountAction": "いいえ、アカウントを削除します",
|
||||
"initiateAccountDeleteTitle": "アカウント削除を開始するには認証してください",
|
||||
"initiateAccountDeleteTitle": "アカウントの削除を開始するためには認証が必要です",
|
||||
"sendEmail": "メール送信",
|
||||
"createNewAccount": "新規アカウント作成",
|
||||
"weakStrength": "脆弱",
|
||||
@@ -157,31 +171,33 @@
|
||||
"moderateStrength": "まあまあ",
|
||||
"confirmPassword": "パスワードの確認",
|
||||
"close": "閉じる",
|
||||
"oopsSomethingWentWrong": "問題が発生しました",
|
||||
"oopsSomethingWentWrong": "おっと、問題が発生しました",
|
||||
"selectLanguage": "言語の選択",
|
||||
"language": "言語",
|
||||
"social": "ソーシャル",
|
||||
"security": "セキュリティ",
|
||||
"lockscreen": "ロックスクリーン",
|
||||
"authToChangeLockscreenSetting": "ロックスクリーン設定を変更するためには認証が必要です",
|
||||
"lockScreenEnablePreSteps": "ロックスクリーンを有効にするには、システム設定でデバイスのパスコードまたはスクリーンロックを設定してください。",
|
||||
"lockscreen": "画面のロック",
|
||||
"authToChangeLockscreenSetting": "画面のロックの設定を変更するためには認証が必要です",
|
||||
"lockScreenEnablePreSteps": "画面のロックを有効にするためには、システム設定でデバイスのパスコードやスクリーンロックを設定してください。",
|
||||
"viewActiveSessions": "アクティブなセッションを表示",
|
||||
"authToViewYourActiveSessions": "アクティブセッションを表示するためには認証が必要です",
|
||||
"authToViewYourActiveSessions": "アクティブなセッションを表示するためには認証が必要です",
|
||||
"searchHint": "検索...",
|
||||
"search": "検索",
|
||||
"sorryUnableToGenCode": "申し訳ありません、 {issuerName} 用のコードを生成できません",
|
||||
"sorryUnableToGenCode": "申し訳ありません、{issuerName} 用のコードを生成できません",
|
||||
"noResult": "該当結果なし",
|
||||
"addCode": "コードを追加",
|
||||
"scanAQrCode": "QRコードをスキャン",
|
||||
"scanAQrCode": "QRコードを読み取り",
|
||||
"enterDetailsManually": "手動で詳細を入力する",
|
||||
"edit": "編集",
|
||||
"copiedToClipboard": "クリップボードにコピーされました",
|
||||
"copiedToClipboard": "クリップボードにコピーしました",
|
||||
"copiedNextToClipboard": "次のコードをクリップボードにコピーしました",
|
||||
"error": "エラー",
|
||||
"recoveryKeyCopiedToClipboard": "回復キーがクリップボードにコピーされました",
|
||||
"recoveryKeyCopiedToClipboard": "回復キーをクリップボードにコピーしました",
|
||||
"recoveryKeyOnForgotPassword": "パスワードを忘れた場合、データを回復できる唯一の方法がこのキーです。",
|
||||
"recoveryKeySaveDescription": "このキーは保存されません。この24単語のキーを安全な場所に保存してください。",
|
||||
"recoveryKeySaveDescription": "私たちはこのキーを保存しません。この 24 単語のキーを安全な場所に保存してください。",
|
||||
"doThisLater": "後で行う",
|
||||
"saveKey": "キーを保存",
|
||||
"back": "戻る",
|
||||
"createAccount": "アカウント作成",
|
||||
"passwordStrength": "パスワードの強度: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
@@ -196,36 +212,36 @@
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"password": "パスワード",
|
||||
"signUpTerms": "<u-terms>利用規約</u-terms> と <u-policy>プライバシー ポリシー</u-policy> に同意します",
|
||||
"signUpTerms": "<u-terms>利用規約</u-terms>と<u-policy>プライバシー ポリシー</u-policy>に同意します",
|
||||
"privacyPolicyTitle": "プライバシーポリシー",
|
||||
"termsOfServicesTitle": "利用規約",
|
||||
"encryption": "暗号化",
|
||||
"setPasswordTitle": "パスワードの設定",
|
||||
"changePasswordTitle": "パスワードの変更",
|
||||
"resetPasswordTitle": "パスワードのリセット",
|
||||
"encryptionKeys": "暗号化キー",
|
||||
"passwordWarning": "私たちはこのパスワードを保存していないため、もしあなたが忘れた場合、 <underline>あなたのデータを復号することはできません</underline>",
|
||||
"encryptionKeys": "暗号鍵",
|
||||
"passwordWarning": "私たちはこのパスワードを保存していないので、あなたがそれを忘れた場合に<underline>私たちがあなたのデータを代わりに復号することはできません</underline>",
|
||||
"enterPasswordToEncrypt": "データの暗号化に使用するパスワードを入力してください",
|
||||
"enterNewPasswordToEncrypt": "データの暗号化に使用する新しいパスワードを入力してください",
|
||||
"passwordChangedSuccessfully": "パスワードを変更しました",
|
||||
"generatingEncryptionKeys": "暗号化キーを生成中...",
|
||||
"generatingEncryptionKeys": "暗号鍵を生成中...",
|
||||
"continueLabel": "続行",
|
||||
"insecureDevice": "安全ではないデバイス",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "申し訳ありませんが、このデバイスでは安全なキーを生成できませんでした。\n\n別のデバイスからサインアップしてください。",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "申し訳ありませんが、このデバイスでは安全な鍵を生成できませんでした。\n\n別のデバイスから登録してください。",
|
||||
"howItWorks": "動作の仕組み",
|
||||
"ackPasswordLostWarning": "私のデータは <underline>エンドツーエンドで暗号化される</underline>ため、パスワードを紛失した場合、データが失われる可能性があることを理解しています。",
|
||||
"loginTerms": "ログインをクリックした場合、 <u-terms>利用規約</u-terms> および <u-policy>プライバシー ポリシー</u-policy> に同意するものとします。",
|
||||
"ackPasswordLostWarning": "私のデータは<underline>エンドツーエンドで暗号化される</underline>ため、パスワードを紛失した場合、データが失われる可能性があることを理解しています。",
|
||||
"loginTerms": "ログインをクリックする場合、<u-terms>利用規約</u-terms>および<u-policy>プライバシー ポリシー</u-policy>に同意するものとします。",
|
||||
"logInLabel": "ログイン",
|
||||
"logout": "ログアウト",
|
||||
"areYouSureYouWantToLogout": "本当にログアウトしてよろしいですか?",
|
||||
"areYouSureYouWantToLogout": "本当にログアウトしてよろしいですか?",
|
||||
"yesLogout": "はい、ログアウトします",
|
||||
"exit": "やめる",
|
||||
"verifyingRecoveryKey": "回復キーを確認中...",
|
||||
"recoveryKeyVerified": "回復キーが確認されました",
|
||||
"recoveryKeySuccessBody": "素晴らしい! 回復キーは有効です。ご確認いただきありがとうございます。\n\n回復キーを安全にバックアップしておいてください。",
|
||||
"invalidRecoveryKey": "入力されたリカバリーキーが無効です。24単語が含まれていることを確認し、それぞれのスペルを確認してください。\n\n古い形式の回復コードを入力した場合は、64文字であることを確認して、それぞれを確認してください。",
|
||||
"recoveryKeySuccessBody": "素晴らしい!回復キーは有効です。ご確認いただきありがとうございます。\n\n回復キーを安全にバックアップしておいてください。",
|
||||
"invalidRecoveryKey": "入力された回復キーが無効です。24 単語が含まれていることを確認し、それぞれのスペルを確認してください。\n\n古い形式の回復コードを入力した場合は、64 文字であることを確認して、それぞれを確認してください。",
|
||||
"recreatePasswordTitle": "パスワードを再設定",
|
||||
"recreatePasswordBody": "現在のデバイスは、パスワードを確認するのに十分ではありません。全てのデバイスで利用できるように再生成する必要があります。\n回復キーを使用してログインし、パスワードを再生成してください (ご希望の場合は再度同じパスワードを使用できます)",
|
||||
"recreatePasswordBody": "現在のデバイスはパスワードを確認するのには不十分ですが、すべてのデバイスで動作するように再生成することはできます。\n\n回復キーを使用してログインし、パスワードを再生成してください(ご希望の場合は同じものを再度使用できます)。",
|
||||
"invalidKey": "キーが無効",
|
||||
"tryAgain": "再試行",
|
||||
"viewRecoveryKey": "回復キーを表示",
|
||||
@@ -241,10 +257,14 @@
|
||||
"tempErrorContactSupportIfPersists": "問題が発生したようです。しばらくしてから再試行してください。エラーが解決しない場合は、サポートチームにお問い合わせください。",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "問題が発生したようです。しばらくしてから再試行してください。エラーが解決しない場合は、サポートチームにお問い合わせください。",
|
||||
"about": "情報",
|
||||
"weAreOpenSource": "我々はオープンソースです!",
|
||||
"weAreOpenSource": "我々はオープンソースです!",
|
||||
"privacy": "プライバシー",
|
||||
"terms": "利用規約",
|
||||
"checkForUpdates": "アップデートを確認",
|
||||
"downloadUpdate": "ダウンロード",
|
||||
"criticalUpdateAvailable": "重要な更新が利用可能です",
|
||||
"updateAvailable": "更新が利用可能です",
|
||||
"update": "更新",
|
||||
"checking": "確認中…",
|
||||
"youAreOnTheLatestVersion": "あなたは最新バージョンを使用しています",
|
||||
"warning": "警告",
|
||||
@@ -254,7 +274,7 @@
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "コードをエクスポートするためには認証が必要です",
|
||||
"importSuccessTitle": "やりました!",
|
||||
"importSuccessTitle": "やりました!",
|
||||
"importSuccessDesc": "{count} 個のコードをインポートしました!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
@@ -266,11 +286,11 @@
|
||||
}
|
||||
},
|
||||
"sorry": "申し訳ありません",
|
||||
"importFailureDesc": "選択したファイルを解析できませんでした。\nsupport@ente.io までご連絡ください。",
|
||||
"importFailureDesc": "選択されたファイルを解析できませんでした。\n手助けが必要な場合は support@ente.io までご連絡ください。",
|
||||
"pendingSyncs": "警告",
|
||||
"pendingSyncsWarningBody": "いくつかのコードがバックアップされていません。\n\nログアウトする前に、これらのコードのバックアップがあることを確認してください。",
|
||||
"checkInboxAndSpamFolder": "受信トレイ (および迷惑メール) を確認して認証を完了してください",
|
||||
"tapToEnterCode": "タップしてコード入力",
|
||||
"checkInboxAndSpamFolder": "受信トレイ(および迷惑メール)を確認して認証を完了してください",
|
||||
"tapToEnterCode": "タップしてコードを入力",
|
||||
"resendEmail": "メールを再送信",
|
||||
"weHaveSendEmailTo": "<green>{email}</green> にメールを送信しました",
|
||||
"@weHaveSendEmailTo": {
|
||||
@@ -285,12 +305,104 @@
|
||||
},
|
||||
"activeSessions": "アクティブセッション",
|
||||
"somethingWentWrongPleaseTryAgain": "問題が発生しました、再試行してください",
|
||||
"thisWillLogYouOutOfThisDevice": "このデバイスからログアウトします!",
|
||||
"thisWillLogYouOutOfThisDevice": "このデバイスからログアウトします!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "以下のデバイスからログアウトします:",
|
||||
"terminateSession": "セッションを終了しますか?",
|
||||
"terminateSession": "セッションを終了しますか?",
|
||||
"terminate": "終了",
|
||||
"thisDevice": "このデバイス",
|
||||
"editCodeAuthMessage": "コードを編集するには認証が必要です",
|
||||
"deleteCodeAuthMessage": "コードを削除するには認証が必要です",
|
||||
"showQRAuthMessage": "QRコードを表示するには認証が必要です"
|
||||
"toResetVerifyEmail": "パスワードをリセットするには、メールの確認を先に行ってください。",
|
||||
"thisEmailIsAlreadyInUse": "このアドレスは既に使用されています",
|
||||
"verificationFailedPleaseTryAgain": "確認に失敗しました、再試行してください",
|
||||
"yourVerificationCodeHasExpired": "確認コードが失効しました",
|
||||
"incorrectCode": "不正なコード",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "申し訳ありませんが、入力されたコードは正しくありません",
|
||||
"emailChangedTo": "メールアドレスが {newEmail} に変更されました",
|
||||
"authenticationFailedPleaseTryAgain": "認証に失敗しました、再試行してください",
|
||||
"authenticationSuccessful": "認証に成功しました!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "2 要素認証は正常にリセットされました",
|
||||
"incorrectRecoveryKey": "不正な回復キー",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "入力された回復キーは正しくありません",
|
||||
"enterPassword": "パスワードを入力",
|
||||
"selectExportFormat": "エクスポートの形式を選択",
|
||||
"exportDialogDesc": "暗号化されたエクスポートはあなたが選択したパスワードで保護されます。",
|
||||
"encrypted": "暗号化済み",
|
||||
"plainText": "平文",
|
||||
"passwordToEncryptExport": "エクスポートを暗号化するためのパスワード",
|
||||
"export": "エクスポート",
|
||||
"useOffline": "バックアップなしで使用する",
|
||||
"signInToBackup": "コードをバックアップするためにサインインする",
|
||||
"singIn": "サインイン",
|
||||
"sigInBackupReminder": "コードをエクスポートして、復元するためのバックアップがあることを確認してください。",
|
||||
"offlineModeWarning": "あなたはバックアップなしに続行することを選択しました。コードを安全に保つために手動でのバックアップを行ってください。",
|
||||
"showLargeIcons": "大きなアイコンを表示",
|
||||
"shouldHideCode": "コードを隠す",
|
||||
"doubleTapToViewHiddenCode": "項目をダブルタップしてコードを閲覧できます",
|
||||
"focusOnSearchBar": "アプリの起動時、検索欄にフォーカスする",
|
||||
"confirmUpdatingkey": "秘密鍵を変更してよろしいですか?",
|
||||
"minimizeAppOnCopy": "コピー時にアプリを最小化する",
|
||||
"editCodeAuthMessage": "コードを編集するためには認証が必要です",
|
||||
"deleteCodeAuthMessage": "コードを削除するためには認証が必要です",
|
||||
"showQRAuthMessage": "QR コードを表示するためには認証が必要です",
|
||||
"confirmAccountDeleteTitle": "アカウントの削除に同意",
|
||||
"confirmAccountDeleteMessage": "このアカウントは他の ente アプリも使用している場合はそれらに結びつけられています。\n\nすべての ente アプリであなたがアップロードしたデータは削除がスケジュールされ、あなたのアカウントは永久に削除されます。",
|
||||
"androidBiometricHint": "本人を確認する",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "認識できません。再試行してください。",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "成功",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "キャンセル",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "認証が必要です",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "生体認証が必要です",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "デバイスの認証情報が必要です",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "デバイスの認証情報が必要です",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "設定を開く",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "生体認証がデバイスで設定されていません。生体認証を追加するには、\"設定 > セキュリティ\"を開いてください。",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "生体認証が無効化されています。画面をロック・ロック解除して生体認証を有効化してください。",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "生体認証がデバイスで設定されていません。Touch ID もしくは Face ID を有効にしてください。",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "インターネット接続なし",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "インターネット接続を確認して、再試行してください。",
|
||||
"signOutFromOtherDevices": "他のデバイスからサインアウトする",
|
||||
"signOutOtherBody": "他の誰かがあなたのパスワードを知っている可能性があると判断した場合は、あなたのアカウントを使用している他のすべてのデバイスから強制的にサインアウトできます。",
|
||||
"signOutOtherDevices": "他のデバイスからサインアウトする",
|
||||
"doNotSignOut": "サインアウトしない",
|
||||
"hearUsWhereTitle": "Ente についてどのようにお聞きになりましたか?(任意)",
|
||||
"hearUsExplanation": "私たちはアプリのインストールを追跡していません。私たちをお知りになった場所を教えてください!"
|
||||
}
|
||||
@@ -1 +1,116 @@
|
||||
{}
|
||||
{
|
||||
"account": "ანგარიში",
|
||||
"unlock": "განბლოკვა",
|
||||
"recoveryKey": "აღდგენის კოდი",
|
||||
"counterAppBarTitle": "მრიცხველზე დაფუძნებული",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "შექმენით თქვენი ორმხრივი აუთენთიფიკაციის კოდების სარეზერვო ასლი უსაფრთხოდ",
|
||||
"onBoardingGetStarted": "დაწყება",
|
||||
"setupFirstAccount": "დააყენეთ თქვენი პირველი ანგარიში",
|
||||
"importScanQrCode": "QR კოდის დასკანერება",
|
||||
"qrCode": "QR კოდი",
|
||||
"importEnterSetupKey": "შეიყვანეთ დაყენების კოდი",
|
||||
"importAccountPageTitle": "შეიყვანეთ ანგარიშის მონაცემები",
|
||||
"secretCanNotBeEmpty": "გასაღების ველი არ შეიძლება ცარიელი იყოს",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "მომწოდებლისა და ანგარიშის ველი არ შეიძლება ცარიელი იყოს",
|
||||
"incorrectDetails": "არასწორი მონაცემები",
|
||||
"pleaseVerifyDetails": "გთხოვთ, გადაამოწმოთ მონაცემები და სცადოთ ხელახლა",
|
||||
"codeIssuerHint": "მომწოდებელი",
|
||||
"codeSecretKeyHint": "გასაღები",
|
||||
"codeAccountHint": "ანგარიში (you@domain.com)",
|
||||
"accountKeyType": "გასაღების ტიპი",
|
||||
"sessionExpired": "სესიის დრო ამოიწურა",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "გთხოვთ, გაიაროთ ავტორიზაცია ხელახლა",
|
||||
"loggingOut": "მიმდინარეობს გამოსვლა...",
|
||||
"timeBasedKeyType": "დროზე დაფუძნებული (TOTP)",
|
||||
"counterBasedKeyType": "მრიცხველზე დაფუძნებული (HOTP)",
|
||||
"saveAction": "შენახვა",
|
||||
"nextTotpTitle": "შემდეგი",
|
||||
"deleteCodeTitle": "გსურთ კოდის წაშლა?",
|
||||
"deleteCodeMessage": "დარწმუნებული ხართ რომ გსურთ ამ კოდის წაშლა? ამ მოქმედების გაუქმება შეუძლებელია.",
|
||||
"viewLogsAction": "აღრიცხვის ფაილების ნახვა",
|
||||
"sendLogsDescription": "თქვენი პრობლემის აღმოსაფხვრელად, ეს ქმედება გააგზავნის აღრიცხვის ფაილებს. მიუხედავად იმისა, რომ ჩვენ ვიღებთ უსაფრთხოების ზომებს, რათა სენსიტიური ინფორმაცია არ მოხვდეს აღრიცხვის ფაილებში, გაგზავნამდე, გირჩევთ, გადახედოთ აღრიცხვის ფაილებს.",
|
||||
"preparingLogsTitle": "მიმდინარეობს აღრიცხვის ფაილების მზადება...",
|
||||
"emailLogsTitle": "აღრიცხვის ფაილების ელექტრონული ფოსტით გაგზავნა",
|
||||
"emailLogsMessage": "გთხოვთ, გამოაგზავნოთ აღრიცხვის ფაილები {email}-ზე",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "ელექტრონული ფოსტის დაკოპირება",
|
||||
"exportLogsAction": "აღრიცხვის ფაილების ექსპორტირება",
|
||||
"reportABug": "პრობლემის შესახებ შეტყობინება",
|
||||
"crashAndErrorReporting": "აპლიკაციის ხარვეზის & პრობლემის შეტყობინება",
|
||||
"reportBug": "პრობლემის შეტყობინება",
|
||||
"emailUsMessage": "გთხოვთ, მოგვწეროთ ელექტრონულ ფოსტაზე {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "მხარდაჭერის გუნდთან დაკავშირება",
|
||||
"rateUsOnStore": "შეგვაფასეთ {storeName}-ზე",
|
||||
"blog": "ბლოგი",
|
||||
"merchandise": "მერჩანტი",
|
||||
"verifyPassword": "პაროლის დასტური",
|
||||
"pleaseWait": "გთხოვთ, დაელოდოთ...",
|
||||
"generatingEncryptionKeysTitle": "მიმდინარეობს დაშიფრვის გასაღებების გენერირება...",
|
||||
"recreatePassword": "პაროლის ხელახლა შექმნა",
|
||||
"incorrectPasswordTitle": "არასწორი პაროლი",
|
||||
"welcomeBack": "კეთილი იყოს თქვენი დაბრუნება!",
|
||||
"madeWithLoveAtPrefix": "შეიქმნა ❤️ ",
|
||||
"changeEmail": "ელექტრონული ფოსტის შეცვლა",
|
||||
"changePassword": "პაროლის შეცვლა",
|
||||
"data": "მონაცემები",
|
||||
"importCodes": "კოდების იმპორტირება",
|
||||
"importTypePlainText": "სტანდარტული ტექსტი",
|
||||
"importTypeEnteEncrypted": "ente დაშიფრული ექსპორტი",
|
||||
"passwordForDecryptingExport": "ექსპორტის გაშიფრვის პაროლი",
|
||||
"passwordEmptyError": "პაროლის ველი არ შეიძლება იყოს ცარიელი",
|
||||
"emailVerificationToggle": "ელექტრონული ფოსტის ვერიფიკაცია",
|
||||
"cancel": "გაუქმება",
|
||||
"yes": "დიახ",
|
||||
"no": "არა",
|
||||
"email": "ელექტრონული ფოსტა",
|
||||
"support": "მხარდაჭერა",
|
||||
"general": "ზოგადი",
|
||||
"settings": "პარამეტრები",
|
||||
"copied": "დაკოპირებულია",
|
||||
"pleaseTryAgain": "გთხოვთ, სცადოთ ხელახლა",
|
||||
"existingUser": "არსებული მომხმარებელი",
|
||||
"delete": "წაშლა",
|
||||
"androidBiometricNotRecognized": "ამოცნობა ვერ მოხერხდა. გთხოვთ, სცადოთ ხელახლა.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "წარმატებით",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "გაუქმება",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "საჭიროა აუთენთიფიკაცია",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"noInternetConnection": "ინტერნეტთან კავშირი არ არის",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "გთხოვთ, შეამოწმოთ თქვენი ინტერნეტ კავშირი და სცადოთ ხელახლა.",
|
||||
"signOutFromOtherDevices": "ყველა მოწყობილობიდან გამოსვლა",
|
||||
"signOutOtherBody": "თუ ფიქრობთ, რომ ვინმემ შესაძლოა იცოდეს თქვენი პაროლი, შეგიძლიათ ყველა მოწყობილობაზე იძულებითი გამოსვლა, რომელიც იყენებს თქვენს ანგარიშს.",
|
||||
"signOutOtherDevices": "სხვა მოწყობილობებიდან გამოსვლა",
|
||||
"doNotSignOut": "არ მოხდეს გამოსვლა",
|
||||
"hearUsWhereTitle": "როგორ შეიტყვეთ Ente-ს შესახებ? (არასავალდებულო)",
|
||||
"hearUsExplanation": "ჩვენ არ ვაკონტროლებთ აპლიკაციის ინსტალაციას. სასარგებლო იქნებოდა, თუ გვეტყოდით, სად გვიპოვეთ!"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Account",
|
||||
"unlock": "Ontgrendelen",
|
||||
"recoveryKey": "Herstelsleutel",
|
||||
"counterAppBarTitle": "Teller",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -83,9 +84,12 @@
|
||||
"importFromApp": "Importeer codes van {appName}",
|
||||
"importGoogleAuthGuide": "Exporteer uw accounts van Google Authenticator naar een QR-code met behulp van de optie \"Transfer Accounts\". Met een ander apparaat scan je de QR-code.\n\nTip: Je kunt de webcam van je laptop gebruiken om een foto van de QR-code te maken.",
|
||||
"importSelectJsonFile": "Selecteer JSON bestand",
|
||||
"importSelectAppExport": "Selecteer {appName} exportbestand",
|
||||
"importEnteEncGuide": "Selecteer het versleutelde JSON-bestand dat vanuit ente geëxporteerd is",
|
||||
"importRaivoGuide": "Gebruik de optie \"Export OTPs to Zip archive\" in Raivo's instellingen.\n\nPak het zip-bestand uit en importeer het JSON-bestand.",
|
||||
"importBitwardenGuide": "Gebruik de optie \"Exporteer kluis\" binnen Bitwarden Tools en importeer het niet-versleutelde JSON-bestand.",
|
||||
"importAegisGuide": "Gebruik de optie \"Exporteer de kluis\" in de instellingen van Aegis.\n\nAls uw kluis is versleuteld, moet u het wachtwoord invoeren om de kluis te ontsleutelen.",
|
||||
"import2FasGuide": "Gebruik de optie \"Instellingen->Backup -Export\" in 2FAS.\n\nAls uw back-up is versleuteld, moet u het wachtwoord invoeren om de back-up te ontsleutelen",
|
||||
"exportCodes": "Codes exporteren",
|
||||
"importLabel": "Importeren",
|
||||
"importInstruction": "Selecteer een bestand dat een lijst van uw codes in de volgende indeling bevat",
|
||||
@@ -97,6 +101,8 @@
|
||||
"authToViewYourRecoveryKey": "Graag verifiëren om uw herstelsleutel te bekijken",
|
||||
"authToChangeYourEmail": "Graag verifiëren om je e-mailadres te wijzigen",
|
||||
"authToChangeYourPassword": "Graag verifiëren om je wachtwoord te wijzigen",
|
||||
"authToViewSecrets": "Graag verifiëren om uw herstelsleutel te bekijken",
|
||||
"authToInitiateSignIn": "Verifiëren om in te kunnen loggen voor back-up.",
|
||||
"ok": "Oké",
|
||||
"cancel": "Annuleer",
|
||||
"yes": "Ja",
|
||||
@@ -329,6 +335,7 @@
|
||||
"offlineModeWarning": "Je hebt ervoor gekozen om verder te gaan zonder back-ups. Neem handmatige back-ups om ervoor te zorgen dat jouw codes veilig zijn.",
|
||||
"showLargeIcons": "Grote iconen",
|
||||
"shouldHideCode": "Verberg codes",
|
||||
"doubleTapToViewHiddenCode": "Je kunt dubbel klikken op een item om code te bekijken",
|
||||
"focusOnSearchBar": "Focus zoekveld na starten app",
|
||||
"confirmUpdatingkey": "Weet u zeker dat u de geheime sleutel wilt bijwerken?",
|
||||
"minimizeAppOnCopy": "Na kopiëren app minimaliseren",
|
||||
@@ -336,5 +343,65 @@
|
||||
"deleteCodeAuthMessage": "Authenticeren om code te verwijderen",
|
||||
"showQRAuthMessage": "Authenticeren om QR-code te tonen",
|
||||
"confirmAccountDeleteTitle": "Account verwijderen bevestigen",
|
||||
"confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten."
|
||||
"confirmAccountDeleteMessage": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\n\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten.",
|
||||
"androidBiometricHint": "Identiteit verifiëren",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Niet herkend. Probeer het opnieuw.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Succes",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Annuleren",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Verificatie vereist",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biometrische verificatie vereist",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Apparaatgegevens vereist",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Apparaatgegevens vereist",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Ga naar instellingen",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Biometrische verificatie is niet ingesteld op uw apparaat. Ga naar 'Instellingen > Beveiliging' om biometrische verificatie toe te voegen.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Biometrische verificatie is uitgeschakeld. Vergrendel en ontgrendel uw scherm om het in te schakelen.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Biometrische authenticatie is niet ingesteld op uw apparaat. Schakel Touch ID of Face ID in op uw telefoon.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "Oké",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "Geen internetverbinding",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Controleer je internetverbinding en probeer het opnieuw.",
|
||||
"signOutFromOtherDevices": "Afmelden bij andere apparaten",
|
||||
"signOutOtherBody": "Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen.",
|
||||
"signOutOtherDevices": "Afmelden bij andere apparaten",
|
||||
"doNotSignOut": "Niet uitloggen",
|
||||
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
|
||||
"hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Konto",
|
||||
"unlock": "Odblokuj",
|
||||
"recoveryKey": "Klucz odzyskiwania",
|
||||
"counterAppBarTitle": "Licznik",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -97,6 +98,7 @@
|
||||
"authToViewYourRecoveryKey": "Proszę uwierzytelnić, aby wyświetlić swój klucz odzyskiwania",
|
||||
"authToChangeYourEmail": "Proszę uwierzytelnić, aby zmienić swój adres e-mail",
|
||||
"authToChangeYourPassword": "Proszę uwierzytelnić, aby zmienić hasło",
|
||||
"authToViewSecrets": "Proszę uwierzytelnić, aby wyświetlić swoje sekrety",
|
||||
"ok": "Ok",
|
||||
"cancel": "Anuluj",
|
||||
"yes": "Tak",
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"account": "Аккаунт",
|
||||
"unlock": "Разблокировать",
|
||||
"recoveryKey": "Ключ восстановления",
|
||||
"counterAppBarTitle": "Счетчик",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "Защитите ваши 2FA коды",
|
||||
"onBoardingBody": "Храните ваши коды двухфакторной аутентификации в безопасности",
|
||||
"onBoardingGetStarted": "Начать",
|
||||
"setupFirstAccount": "Настройте свой первый аккаунт",
|
||||
"setupFirstAccount": "Настройте свою первую учетную запись",
|
||||
"importScanQrCode": "Сканировать QR-код",
|
||||
"qrCode": "QR-код",
|
||||
"importEnterSetupKey": "Ввести ключ настройки",
|
||||
@@ -85,6 +86,7 @@
|
||||
"importSelectJsonFile": "Выбрать JSON-файл",
|
||||
"importEnteEncGuide": "Выберите зашифрованный JSON-файл, экспортированный из ente",
|
||||
"importRaivoGuide": "Используйте опцию «Export OTPs to Zip archive» в настройках Raivo.\n\nРаспакуйте zip-архив и импортируйте JSON-файл.",
|
||||
"importBitwardenGuide": "Используйте опцию \"Экспортировать хранилище\" в Bitwarden Tools и импортируйте незашифрованный JSON файл.",
|
||||
"importAegisGuide": "Используйте опцию «Экспортировать хранилище» в настройках Aegis.\n\nЕсли ваше хранилище зашифровано, то для его расшифровки потребуется ввести пароль хранилища.",
|
||||
"exportCodes": "Экспортировать коды",
|
||||
"importLabel": "Импорт",
|
||||
@@ -97,6 +99,7 @@
|
||||
"authToViewYourRecoveryKey": "Пожалуйста, авторизуйтесь для просмотра вашего ключа восстановления",
|
||||
"authToChangeYourEmail": "Пожалуйста, авторизуйтесь, чтобы изменить адрес электронной почты",
|
||||
"authToChangeYourPassword": "Пожалуйста, авторизуйтесь, чтобы изменить пароль",
|
||||
"authToViewSecrets": "Пожалуйста, авторизуйтесь для просмотра ваших секретов",
|
||||
"ok": "Ок",
|
||||
"cancel": "Отменить",
|
||||
"yes": "Да",
|
||||
@@ -113,7 +116,7 @@
|
||||
"enterYourPasswordHint": "Введите пароль",
|
||||
"forgotPassword": "Забыл пароль",
|
||||
"oops": "Ой",
|
||||
"suggestFeatures": "Предложить улучшения",
|
||||
"suggestFeatures": "Предложить идеи",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "Насколько безопасен ente Auth?",
|
||||
"faq_a_1": "Все коды, которые вы резервируете с помощью ente, хранятся в зашифрованном виде. Это означает, что только вы можете получить доступ к своим кодам. Наши приложения имеют открытый исходный код, а наша криптография прошла внешний аудит.",
|
||||
@@ -167,7 +170,7 @@
|
||||
"oopsSomethingWentWrong": "Ой, что-то пошло не так.",
|
||||
"selectLanguage": "Выберите язык",
|
||||
"language": "Язык",
|
||||
"social": "Соцсети",
|
||||
"social": "Социальные сети",
|
||||
"security": "Безопасность",
|
||||
"lockscreen": "Экран блокировки",
|
||||
"authToChangeLockscreenSetting": "Пожалуйста, авторизуйтесь, чтобы изменить настройки экрана блокировки",
|
||||
@@ -303,7 +306,7 @@
|
||||
"terminateSession": "Завершить сеанс?",
|
||||
"terminate": "Завершить",
|
||||
"thisDevice": "Это устройство",
|
||||
"toResetVerifyEmail": "Чтобы сбросить пароль, сначала подтвердите свой адрес электронной почты.",
|
||||
"toResetVerifyEmail": "Подтвердите адрес электронной почты, чтобы сбросить пароль.",
|
||||
"thisEmailIsAlreadyInUse": "Этот адрес электронной почты уже используется",
|
||||
"verificationFailedPleaseTryAgain": "Проверка не удалась, попробуйте еще раз",
|
||||
"yourVerificationCodeHasExpired": "Срок действия вашего проверочного кода истек",
|
||||
@@ -327,14 +330,75 @@
|
||||
"singIn": "Войти",
|
||||
"sigInBackupReminder": "Экспортируйте свои коды, чтобы убедиться, что у вас есть резервная копия, из которой можно восстановить.",
|
||||
"offlineModeWarning": "Вы решили продолжить без резервных копий. Пожалуйста, создайте резервные копии вручную, чтобы убедиться, что ваши коды в безопасности.",
|
||||
"showLargeIcons": "Показать большие значки",
|
||||
"showLargeIcons": "Использовать большие значки",
|
||||
"shouldHideCode": "Скрыть коды",
|
||||
"doubleTapToViewHiddenCode": "Вы можете нажать дважды на запись для просмотра кода",
|
||||
"focusOnSearchBar": "Фокусировать поиск при запуске приложения",
|
||||
"confirmUpdatingkey": "Вы уверены, что хотите обновить секретный ключ?",
|
||||
"minimizeAppOnCopy": "Свернуть приложение при копировании",
|
||||
"minimizeAppOnCopy": "Сворачивать приложение при копировании",
|
||||
"editCodeAuthMessage": "Аутентификация для редактирования кода",
|
||||
"deleteCodeAuthMessage": "Аутентификация для удаления кода",
|
||||
"showQRAuthMessage": "Аутентификация для отображения QR-кода",
|
||||
"confirmAccountDeleteTitle": "Подтвердить удаление аккаунта",
|
||||
"confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления."
|
||||
"confirmAccountDeleteMessage": "Эта учетная запись связана с другими приложениями ente, если вы ими пользуетесь.\n\nЗагруженные вами данные во всех приложениях ente будут запланированы к удалению, а ваша учетная запись будет удалена без возможности восстановления.",
|
||||
"androidBiometricHint": "Подтвердите личность",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Не распознано. Попробуйте еще раз.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Успешно",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Отменить",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Требуется аутентификация",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Требуется биометрия",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Требуются учетные данные устройства",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Требуются учетные данные устройства",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Перейдите к настройкам",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Перейдите в \"Настройки > Безопасность\", чтобы добавить биометрическую аутентификацию.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Биометрическая аутентификация отключена. Пожалуйста, заблокируйте и разблокируйте экран, чтобы включить ее.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Биометрическая аутентификация не настроена на вашем устройстве. Пожалуйста, включите Touch ID или Face ID на вашем телефоне.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"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."
|
||||
},
|
||||
"noInternetConnection": "Нет подключения к Интернету",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Проверьте подключение к Интернету и повторите попытку.",
|
||||
"signOutFromOtherDevices": "Выйти из других устройств",
|
||||
"signOutOtherBody": "Если вы думаете, что кто-то может знать ваш пароль, вы можете принудительно выйти из всех устройств.",
|
||||
"signOutOtherDevices": "Выйти из других устройств",
|
||||
"doNotSignOut": "Не выходить",
|
||||
"hearUsWhereTitle": "Как вы узнали о Ente? (необязательно)",
|
||||
"hearUsExplanation": "Будет полезно, если вы укажете, где нашли нас, так как мы не отслеживаем установки приложения"
|
||||
}
|
||||
407
lib/l10n/arb/app_ti.arb
Normal file
@@ -0,0 +1,407 @@
|
||||
{
|
||||
"account": "ጸብጻብ",
|
||||
"unlock": "ከፈተ",
|
||||
"recoveryKey": "ምሕዋይ መፍትሕ",
|
||||
"counterAppBarTitle": "ቆጻሪ",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "2FA ኮድካ ብውሑስ ኣቐምጦ",
|
||||
"onBoardingGetStarted": "ጀምር",
|
||||
"setupFirstAccount": "ናይ መጀመርታ ሕሳብካ ኣዳል",
|
||||
"importScanQrCode": "QR ኮድ ስካን ግበር",
|
||||
"qrCode": "ኪዊኣር ስርዓት",
|
||||
"importEnterSetupKey": "ምድላው መፍትሕ ኣእቱ",
|
||||
"importAccountPageTitle": "ዝርዝር ሕሳብ ኣእትዉ",
|
||||
"secretCanNotBeEmpty": "ምስጢር ባዶ ኪኸውን ኣይክእልን እዩ",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "እቲ ኣወሃሃዲ ዀነ እቲ ሕሳብ ባዶ ኪኸውን ኣይክእልን እዩ",
|
||||
"incorrectDetails": "ጌጋ ዝርዝር-ሓበሬታ",
|
||||
"pleaseVerifyDetails": "በጃኹም ዝርዝር-ሓበሬታ ኣረጋግጹ እሞ እንደገና ፈትኑ",
|
||||
"codeIssuerHint": "ኣዋጂ",
|
||||
"codeSecretKeyHint": "ምስጢራዊ መፍትሕ",
|
||||
"codeAccountHint": "ሕሳብ (you@domain.com)",
|
||||
"accountKeyType": "ዓይነት መፍትሕ",
|
||||
"sessionExpired": "ክፍለ ግዜኡ ኣኺሉ።",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "በጃኹም እንደገና እቶ",
|
||||
"loggingOut": "ወጸ...",
|
||||
"timeBasedKeyType": "ግዜ እተመስረተ (TOTP)",
|
||||
"counterBasedKeyType": "ቆጻሪ እተመስረተ (TOTP)",
|
||||
"saveAction": "",
|
||||
"nextTotpTitle": "ቀጽሊ",
|
||||
"deleteCodeTitle": "ኮድ ምድምሳስ፧",
|
||||
"deleteCodeMessage": "ነዚ ኮድ ክትድምስሶ ከም እትደሊ ርግጸኛ ዲኻ፧ እዚ ተግባር ንድሕሪት ዘይምለስ እዩ።",
|
||||
"viewLogsAction": "ምዝገባታት ርአ",
|
||||
"sendLogsDescription": "",
|
||||
"preparingLogsTitle": "ምዝገባ ድላው...",
|
||||
"emailLogsTitle": "መዝገብ ኢ-መይል",
|
||||
"emailLogsMessage": "በጃኹም ነቲ መዝገብ ናብ {email} ስደዱሉ",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "ኢ-መይል ቀዲሕካ",
|
||||
"exportLogsAction": "ምዝገባታት ሰደድ",
|
||||
"reportABug": "ጌጋ ጸብጻብ ልኣኸ",
|
||||
"crashAndErrorReporting": "ጸብጻብ ውድቀትን ጌጋን",
|
||||
"reportBug": "ጌጋ ጸብጻብ ልኣኸ",
|
||||
"emailUsMessage": "በጃኹም ኢ-መይል ጽሓፉልና {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "ደገፍ ኣድራሻ",
|
||||
"rateUsOnStore": "ብዛዕባና ርእይቶ ኣብ {storeName} ኣካፍሎ",
|
||||
"blog": "ብሎግ",
|
||||
"merchandise": "ሸቐጥ",
|
||||
"verifyPassword": "ቃለ-ምስጢር ኣረጋግጽ",
|
||||
"pleaseWait": "በጃኻ ተጸበ...",
|
||||
"generatingEncryptionKeysTitle": "ናይ ምስጢራዊ ቁልፊ ዪፍጠር...",
|
||||
"recreatePassword": "ቃለ-ምስጢር እንደገና ፍጠር",
|
||||
"recreatePasswordMessage": "እዛ ሕጂ ዘላ ኤለክትሮኒካዊት መሳርሒት ነቲ passwordካ ንምርግጋጽ እኹል ሓይሊ ስለ ዘይብላ ምስ ኵሉ መሳርሒታት ብዚሰማማዕ መገዲ ሓንሳእ እንደገና ኸነሐድሶ ኣሎና ።\n\nበጃኻ በቲ ምሕዋይ-መፍትሕ ኣቲኻ ቃለ-ምስጢር ኣሐድሶ (እንተ ደሊኻ ነታ ቃለ-ምስጢር እንደገና ኽትጥቀመላ ትኽእል ኢኻ)።",
|
||||
"useRecoveryKey": "ምሕዋይ መፍትሕ ተጠቐም",
|
||||
"incorrectPasswordTitle": "ግጉይ ቃለ-ምስጢር",
|
||||
"welcomeBack": "እንኳዕ ብደሓን ተመለስካ!",
|
||||
"madeWithLoveAtPrefix": "ምስ ❤️ ዝተሰርሐ ኣብ ",
|
||||
"supportDevs": "ንዓና ንምድጋፍ ኣብ <bold-green>ente</bold-green> ተሓወስ ግበሩ",
|
||||
"supportDiscount": "ኣብ ቀዳማይ ዓመት 10% ቅናስ ንምርካብ \"AUTH\" ኮድ ተጠቐም",
|
||||
"changeEmail": "Change email",
|
||||
"changePassword": "ቃለ-ምስጢር ቀያይር",
|
||||
"data": "ሓበሬታ",
|
||||
"importCodes": "ኮድ ኣእቱ",
|
||||
"importTypePlainText": "Plain text",
|
||||
"importTypeEnteEncrypted": "ente Encrypted export",
|
||||
"passwordForDecryptingExport": "Password to decrypt export",
|
||||
"passwordEmptyError": "Password can not be empty",
|
||||
"importFromApp": "Import codes from {appName}",
|
||||
"importGoogleAuthGuide": "Export your accounts from Google Authenticator to a QR code using the \"Transfer Accounts\" option. Then using another device, scan the QR code.\n\nTip: You can use your laptop's webcam to take a picture of the QR code.",
|
||||
"importSelectJsonFile": "Select JSON file",
|
||||
"importSelectAppExport": "Select {appName} export file",
|
||||
"importEnteEncGuide": "Select the encrypted JSON file exported from ente",
|
||||
"importRaivoGuide": "Use the \"Export OTPs to Zip archive\" option in Raivo's Settings.\n\nExtract the zip file and import the JSON file.",
|
||||
"importBitwardenGuide": "Use the \"Export vault\" option within Bitwarden Tools and import the unencrypted JSON file.",
|
||||
"importAegisGuide": "Use the \"Export the vault\" option in Aegis's Settings.\n\nIf your vault is encrypted, you will need to enter vault password to decrypt the vault.",
|
||||
"import2FasGuide": "Use the \"Settings->Backup -Export\" option in 2FAS.\n\nIf your backup is encrypted, you will need to enter the password to decrypt the backup",
|
||||
"exportCodes": "Export codes",
|
||||
"importLabel": "Import",
|
||||
"importInstruction": "Please select a file that contains a list of your codes in the following format",
|
||||
"importCodeDelimiterInfo": "The codes can be separated by a comma or a new line",
|
||||
"selectFile": "Select file",
|
||||
"emailVerificationToggle": "Email verification",
|
||||
"emailVerificationEnableWarning": "To avoid getting locked out of your account, be sure to store a copy of your email 2FA outside of Ente Auth before enabling email verification.",
|
||||
"authToChangeEmailVerificationSetting": "Please authenticate to change email verification",
|
||||
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
|
||||
"authToChangeYourEmail": "Please authenticate to change your email",
|
||||
"authToChangeYourPassword": "Please authenticate to change your password",
|
||||
"authToViewSecrets": "Please authenticate to view your secrets",
|
||||
"authToInitiateSignIn": "Please authenticate to initiate sign in for backup.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Cancel",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"email": "Email",
|
||||
"support": "Support",
|
||||
"general": "General",
|
||||
"settings": "Settings",
|
||||
"copied": "Copied",
|
||||
"pleaseTryAgain": "Please try again",
|
||||
"existingUser": "Existing User",
|
||||
"newUser": "New to ente",
|
||||
"delete": "Delete",
|
||||
"enterYourPasswordHint": "Enter your password",
|
||||
"forgotPassword": "Forgot password",
|
||||
"oops": "ዉዉኡ",
|
||||
"suggestFeatures": "Suggest features",
|
||||
"faq": "FAQ",
|
||||
"faq_q_1": "How secure is ente Auth?",
|
||||
"faq_a_1": "All codes you backup via ente is stored end-to-end encrypted. This means only you can access your codes. Our apps are open source and our cryptography has been externally audited.",
|
||||
"faq_q_2": "Can I access my codes on desktop?",
|
||||
"faq_a_2": "You can access your codes on the web @ auth.ente.io.",
|
||||
"faq_q_3": "How can I delete codes?",
|
||||
"faq_a_3": "You can delete a code by swiping left on that item.",
|
||||
"faq_q_4": "How can I support this project?",
|
||||
"faq_a_4": "You can support the development of this project by subscribing to our Photos app @ ente.io.",
|
||||
"faq_q_5": "How can I enable FaceID lock in ente Auth",
|
||||
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
|
||||
"somethingWentWrongMessage": "Something went wrong, please try again",
|
||||
"leaveFamily": "Leave family",
|
||||
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
|
||||
"inFamilyPlanMessage": "You are on a family plan!",
|
||||
"swipeHint": "Swipe left to edit or remove codes",
|
||||
"scan": "Scan",
|
||||
"scanACode": "Scan a code",
|
||||
"verify": "Verify",
|
||||
"verifyEmail": "Verify email",
|
||||
"enterCodeHint": "Enter the 6-digit code from\nyour authenticator app",
|
||||
"lostDeviceTitle": "Lost device?",
|
||||
"twoFactorAuthTitle": "Two-factor authentication",
|
||||
"recoverAccount": "Recover account",
|
||||
"enterRecoveryKeyHint": "Enter your recovery key",
|
||||
"recover": "Recover",
|
||||
"contactSupportViaEmailMessage": "Please drop an email to {email} from your registered email address",
|
||||
"@contactSupportViaEmailMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"noRecoveryKeyTitle": "No recovery key?",
|
||||
"enterEmailHint": "Enter your email address",
|
||||
"invalidEmailTitle": "Invalid email address",
|
||||
"invalidEmailMessage": "Please enter a valid email address.",
|
||||
"deleteAccount": "Delete account",
|
||||
"deleteAccountQuery": "We'll be sorry to see you go. Are you facing some issue?",
|
||||
"yesSendFeedbackAction": "Yes, send feedback",
|
||||
"noDeleteAccountAction": "No, delete account",
|
||||
"initiateAccountDeleteTitle": "Please authenticate to initiate account deletion",
|
||||
"sendEmail": "Send email",
|
||||
"createNewAccount": "Create new account",
|
||||
"weakStrength": "Weak",
|
||||
"strongStrength": "Strong",
|
||||
"moderateStrength": "Moderate",
|
||||
"confirmPassword": "Confirm password",
|
||||
"close": "Close",
|
||||
"oopsSomethingWentWrong": "Oops, Something went wrong.",
|
||||
"selectLanguage": "Select language",
|
||||
"language": "ቋንቋ",
|
||||
"social": "ማሕበራዊ",
|
||||
"security": "ድሕንነት",
|
||||
"lockscreen": "ስክሪን ምዕጻው",
|
||||
"authToChangeLockscreenSetting": "Please authenticate to change lockscreen setting",
|
||||
"lockScreenEnablePreSteps": "To enable lockscreen, please setup device passcode or screen lock in your system settings.",
|
||||
"viewActiveSessions": "View active sessions",
|
||||
"authToViewYourActiveSessions": "Please authenticate to view your active sessions",
|
||||
"searchHint": "Search...",
|
||||
"search": "Search",
|
||||
"sorryUnableToGenCode": "Sorry, unable to generate a code for {issuerName}",
|
||||
"noResult": "No result",
|
||||
"addCode": "Add code",
|
||||
"scanAQrCode": "Scan a QR code",
|
||||
"enterDetailsManually": "Enter details manually",
|
||||
"edit": "Edit",
|
||||
"copiedToClipboard": "Copied to clipboard",
|
||||
"copiedNextToClipboard": "Copied next code to clipboard",
|
||||
"error": "Error",
|
||||
"recoveryKeyCopiedToClipboard": "Recovery key copied to clipboard",
|
||||
"recoveryKeyOnForgotPassword": "If you forget your password, the only way you can recover your data is with this key.",
|
||||
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
|
||||
"doThisLater": "Do this later",
|
||||
"saveKey": "Save key",
|
||||
"back": "Back",
|
||||
"createAccount": "Create account",
|
||||
"passwordStrength": "Password strength: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
"placeholders": {
|
||||
"passwordStrengthValue": {
|
||||
"description": "The strength of the password as a string",
|
||||
"type": "String",
|
||||
"example": "Weak or Moderate or Strong"
|
||||
}
|
||||
},
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"password": "Password",
|
||||
"signUpTerms": "I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>",
|
||||
"privacyPolicyTitle": "Privacy Policy",
|
||||
"termsOfServicesTitle": "Terms",
|
||||
"encryption": "Encryption",
|
||||
"setPasswordTitle": "Set password",
|
||||
"changePasswordTitle": "Change password",
|
||||
"resetPasswordTitle": "Reset password",
|
||||
"encryptionKeys": "Encryption keys",
|
||||
"passwordWarning": "We don't store this password, so if you forget, <underline>we cannot decrypt your data</underline>",
|
||||
"enterPasswordToEncrypt": "Enter a password we can use to encrypt your data",
|
||||
"enterNewPasswordToEncrypt": "Enter a new password we can use to encrypt your data",
|
||||
"passwordChangedSuccessfully": "Password changed successfully",
|
||||
"generatingEncryptionKeys": "Generating encryption keys...",
|
||||
"continueLabel": "Continue",
|
||||
"insecureDevice": "Insecure device",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Sorry, we could not generate secure keys on this device.\n\nplease sign up from a different device.",
|
||||
"howItWorks": "How it works",
|
||||
"ackPasswordLostWarning": "I understand that if I lose my password, I may lose my data since my data is <underline>end-to-end encrypted</underline>.",
|
||||
"loginTerms": "By clicking log in, I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>",
|
||||
"logInLabel": "Log in",
|
||||
"logout": "Logout",
|
||||
"areYouSureYouWantToLogout": "Are you sure you want to logout?",
|
||||
"yesLogout": "Yes, logout",
|
||||
"exit": "Exit",
|
||||
"verifyingRecoveryKey": "Verifying recovery key...",
|
||||
"recoveryKeyVerified": "Recovery key verified",
|
||||
"recoveryKeySuccessBody": "Great! Your recovery key is valid. Thank you for verifying.\n\nPlease remember to keep your recovery key safely backed up.",
|
||||
"invalidRecoveryKey": "The recovery key you entered is not valid. Please make sure it contains 24 words, and check the spelling of each.\n\nIf you entered an older recovery code, make sure it is 64 characters long, and check each of them.",
|
||||
"recreatePasswordTitle": "Recreate password",
|
||||
"recreatePasswordBody": "The current device is not powerful enough to verify your password, but we can regenerate in a way that works with all devices.\n\nPlease login using your recovery key and regenerate your password (you can use the same one again if you wish).",
|
||||
"invalidKey": "Invalid key",
|
||||
"tryAgain": "Try again",
|
||||
"viewRecoveryKey": "View recovery key",
|
||||
"confirmRecoveryKey": "Confirm recovery key",
|
||||
"recoveryKeyVerifyReason": "Your recovery key is the only way to recover your photos if you forget your password. You can find your recovery key in Settings > Account.\n\nPlease enter your recovery key here to verify that you have saved it correctly.",
|
||||
"confirmYourRecoveryKey": "Confirm your recovery key",
|
||||
"confirm": "Confirm",
|
||||
"emailYourLogs": "Email your logs",
|
||||
"pleaseSendTheLogsTo": "Please send the logs to \n{toEmail}",
|
||||
"copyEmailAddress": "Copy email address",
|
||||
"exportLogs": "Export logs",
|
||||
"enterYourRecoveryKey": "Enter your recovery key",
|
||||
"tempErrorContactSupportIfPersists": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
|
||||
"about": "About",
|
||||
"weAreOpenSource": "We are open source!",
|
||||
"privacy": "Privacy",
|
||||
"terms": "Terms",
|
||||
"checkForUpdates": "Check for updates",
|
||||
"downloadUpdate": "Download",
|
||||
"criticalUpdateAvailable": "Critical update available",
|
||||
"updateAvailable": "Update available",
|
||||
"update": "Update",
|
||||
"checking": "Checking...",
|
||||
"youAreOnTheLatestVersion": "You are on the latest version",
|
||||
"warning": "Warning",
|
||||
"exportWarningDesc": "The exported file contains sensitive information. Please store this safely.",
|
||||
"iUnderStand": "I understand",
|
||||
"@iUnderStand": {
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "Please authenticate to export your codes",
|
||||
"importSuccessTitle": "Yay!",
|
||||
"importSuccessDesc": "You have imported {count} codes!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorry": "Sorry",
|
||||
"importFailureDesc": "Could not parse the selected file.\nPlease write to support@ente.io if you need help!",
|
||||
"pendingSyncs": "Warning",
|
||||
"pendingSyncsWarningBody": "Some of your codes have not been backed up.\n\nPlease ensure that you have a backup for these codes before you logout.",
|
||||
"checkInboxAndSpamFolder": "Please check your inbox (and spam) to complete verification",
|
||||
"tapToEnterCode": "Tap to enter code",
|
||||
"resendEmail": "Resend email",
|
||||
"weHaveSendEmailTo": "We have sent a mail to <green>{email}</green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"description": "The email address of the user",
|
||||
"type": "String",
|
||||
"example": "example@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"activeSessions": "Active sessions",
|
||||
"somethingWentWrongPleaseTryAgain": "Something went wrong, please try again",
|
||||
"thisWillLogYouOutOfThisDevice": "This will log you out of this device!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "This will log you out of the following device:",
|
||||
"terminateSession": "Terminate session?",
|
||||
"terminate": "Terminate",
|
||||
"thisDevice": "This device",
|
||||
"toResetVerifyEmail": "To reset your password, please verify your email first.",
|
||||
"thisEmailIsAlreadyInUse": "This email is already in use",
|
||||
"verificationFailedPleaseTryAgain": "Verification failed, please try again",
|
||||
"yourVerificationCodeHasExpired": "Your verification code has expired",
|
||||
"incorrectCode": "Incorrect code",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Sorry, the code you've entered is incorrect",
|
||||
"emailChangedTo": "Email changed to {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Authentication failed, please try again",
|
||||
"authenticationSuccessful": "Authentication successful!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Two-factor authentication successfully reset",
|
||||
"incorrectRecoveryKey": "Incorrect recovery key",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "The recovery key you entered is incorrect",
|
||||
"enterPassword": "Enter password",
|
||||
"selectExportFormat": "Select export format",
|
||||
"exportDialogDesc": "Encrypted exports will be protected by a password of your choice.",
|
||||
"encrypted": "Encrypted",
|
||||
"plainText": "Plain text",
|
||||
"passwordToEncryptExport": "Password to encrypt export",
|
||||
"export": "Export",
|
||||
"useOffline": "Use without backups",
|
||||
"signInToBackup": "Sign in to backup your codes",
|
||||
"singIn": "Sign in",
|
||||
"sigInBackupReminder": "Please export your codes to ensure that you have a backup you can restore from.",
|
||||
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
|
||||
"showLargeIcons": "Show large icons",
|
||||
"shouldHideCode": "Hide codes",
|
||||
"doubleTapToViewHiddenCode": "You can double tap on an entry to view code",
|
||||
"focusOnSearchBar": "Focus search on app start",
|
||||
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
|
||||
"minimizeAppOnCopy": "Minimize app on copy",
|
||||
"editCodeAuthMessage": "Authenticate to edit code",
|
||||
"deleteCodeAuthMessage": "Authenticate to delete code",
|
||||
"showQRAuthMessage": "Authenticate to show QR code",
|
||||
"confirmAccountDeleteTitle": "Confirm account deletion",
|
||||
"confirmAccountDeleteMessage": "This account is linked to other ente apps, if you use any.\n\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
|
||||
"androidBiometricHint": "Verify identity",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Not recognized. Try again.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Success",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Cancel",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Authentication required",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biometric required",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Device credentials required",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Device credentials required",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Go to settings",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Biometric authentication is not set up on your device. Go to 'Settings > Security' to add biometric authentication.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Biometric authentication is disabled. Please lock and unlock your screen to enable it.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Biometric authentication is not set up on your device. Please either enable Touch ID or Face ID on your phone.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "No internet connection",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.",
|
||||
"signOutFromOtherDevices": "Sign out from other devices",
|
||||
"signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.",
|
||||
"signOutOtherDevices": "ካብ ካልኦት መሳርሒታት ኣውጽኡኒ።",
|
||||
"doNotSignOut": "ኣውጽኡኒ።",
|
||||
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
|
||||
"hearUsExplanation": "ተጠቀምትና ኣይንከታተልን ኢና። ኣበይ ከም ዝረኸብካና እንተ ትነግረና ሓጋዚ እዩ፦"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"account": "Hesabım",
|
||||
"unlock": "Kilidi aç",
|
||||
"recoveryKey": "Kurtarma Anahtarı",
|
||||
"counterAppBarTitle": "Sayaç",
|
||||
"@counterAppBarTitle": {
|
||||
@@ -12,11 +13,13 @@
|
||||
"qrCode": "QR Kodu",
|
||||
"importEnterSetupKey": "Kurulum anahtarını giriniz",
|
||||
"importAccountPageTitle": "Hesap bilgilerinizi girin",
|
||||
"secretCanNotBeEmpty": "Gizli anahtar boş olamaz",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "Sağlayıcı adı ve hesap değerleri boş olamaz",
|
||||
"incorrectDetails": "Bilgiler yanlış",
|
||||
"pleaseVerifyDetails": "Lütfen bilgileri doğrulayın ve tekrar deneyin",
|
||||
"codeIssuerHint": "Yayınlayan",
|
||||
"codeSecretKeyHint": "Gizli Anahtar",
|
||||
"codeAccountHint": "Hesap (örnek@domain.com)",
|
||||
"codeAccountHint": "Hesap (ornek@domain.com)",
|
||||
"accountKeyType": "Anahtar türü",
|
||||
"sessionExpired": "Oturum süresi doldu",
|
||||
"@sessionExpired": {
|
||||
@@ -33,7 +36,7 @@
|
||||
"viewLogsAction": "Günlüğü görüntüle",
|
||||
"sendLogsDescription": "Günlüğünüz hatanızı çözmemize yardımcı olacaktır. Hassas bilginin kaydedilmediğine dikkat etsek de bu günlükleri paylaşmadan önce kontrol etmenizi isteriz.",
|
||||
"preparingLogsTitle": "Günlük hazırlanıyor...",
|
||||
"emailLogsTitle": "E-postaların Günlüğü",
|
||||
"emailLogsTitle": "Günlüğü e-posta olarak gönder",
|
||||
"emailLogsMessage": "Lütfen günlüğünüzü {email} adresine gönderin",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
@@ -67,39 +70,46 @@
|
||||
"useRecoveryKey": "Kurtarma anahtarını kullan",
|
||||
"incorrectPasswordTitle": "Yanlış şifre",
|
||||
"welcomeBack": "Tekrar hoş geldiniz!",
|
||||
"madeWithLoveAtPrefix": "❤️ ile yapılmıştır ",
|
||||
"supportDevs": "Bu projeyi desteklemek için <bold-green>ente</bold-green> kanalına abone olun.",
|
||||
"madeWithLoveAtPrefix": "❤️ ile şurada yapılmıştır: ",
|
||||
"supportDevs": "Bu projeyi desteklemek için <bold-green>ente</bold-green> kanalına abone olun",
|
||||
"supportDiscount": "İlk yılda %10 indirim için \"AUTH\" kupon kodunu kullanın",
|
||||
"changeEmail": "E-postayı değiştir",
|
||||
"changeEmail": "E-posta'yı değiştir",
|
||||
"changePassword": "Şifreyi değiştir",
|
||||
"data": "Veriler",
|
||||
"data": "Veri",
|
||||
"importCodes": "Kodu içe aktar",
|
||||
"importTypePlainText": "Düz metin",
|
||||
"importTypePlainText": "Salt metin",
|
||||
"importTypeEnteEncrypted": "ente Şifreli dışa aktarma",
|
||||
"passwordForDecryptingExport": "Dışa aktarımın şifresini çözmek için parola",
|
||||
"passwordEmptyError": "Şifre boş olamaz",
|
||||
"importFromApp": "Kodları {appName} uygulamasından içe aktarın",
|
||||
"importGoogleAuthGuide": "\"Hesapları Aktar\" seçeneğini kullanarak hesaplarınızı Google Authenticator'dan bir QR koduna aktarın. Ardından başka bir cihaz kullanarak QR kodunu tarayın.\n\nİpucu: QR kodunun fotoğrafını çekmek için dizüstü bilgisayarınızın kamerasını kullanabilirsiniz.",
|
||||
"importSelectJsonFile": "JSON dosyasını seçin",
|
||||
"importSelectAppExport": "{appName} dışarı aktarma dosyasını seçin",
|
||||
"importEnteEncGuide": "Ente'den dışa aktarılan şifrelenmiş JSON dosyasını seçin",
|
||||
"importRaivoGuide": "Raivo'nun ayarlarında \"OTP'leri Zip arşivine aktar\" seçeneğini kullanın.\n\nZip dosyasını çıkarın ve JSON dosyasını içe aktarın.",
|
||||
"importBitwardenGuide": "Bitwarden Tools içindeki \"Kasayı dışa aktar\" seçeneğini kullanın ve şifrelenmemiş JSON dosyasını içe aktarın.",
|
||||
"importAegisGuide": "Aegis'in Ayarlarında \"Kasayı dışa aktar\" seçeneğini kullanın.\n\nKasanız şifrelenmişse, kasanın şifresini çözmek için kasa parolasını girmeniz gerekecektir.",
|
||||
"import2FasGuide": "2FAS içindeki \"Ayarlar -> Yedekle-Dışa Aktar\" seçeneğini seçin.\n\nEğer yedeğiniz şifreli ise, şifrenin çözülmesi için şifreleme parolasını girmeniz gerekmekte",
|
||||
"exportCodes": "Kodu dışa aktar",
|
||||
"importLabel": "İçe aktar",
|
||||
"importInstruction": "Lütfen aşağıdaki şekilde kodlarınızın listesini içeren dosyayı seçiniz",
|
||||
"importCodeDelimiterInfo": "Kodlar, virgülle ya da yeni bir satırla ayrılabilir",
|
||||
"selectFile": "Dosya seç",
|
||||
"emailVerificationToggle": "E-posta doğrulama",
|
||||
"emailVerificationEnableWarning": "E-postanız için 2FA'yı bizde saklıyorsanız, e-posta doğrulamasını açmak bir kilitlenmeye neden olabilir. Bir hizmetin dışında kalırsanız, diğerine giriş yapamayabilirsiniz.",
|
||||
"authToChangeEmailVerificationSetting": "E-posta doğrulamasını değiştirmek için lütfen kimlik doğrulaması yapın",
|
||||
"authToViewYourRecoveryKey": "Kurtarma anahtarınızı görmek için lütfen kimliğinizi doğrulayın",
|
||||
"authToChangeYourEmail": "Epostanızı değiştirmek için lütfen kimliğinizi doğrulayın",
|
||||
"authToChangeYourPassword": "Şifrenizi değiştirmek için lütfen kimliğinizi doğrulayın",
|
||||
"authToViewSecrets": "Kodlarınızı görmek için lütfen kimlik doğrulaması yapın",
|
||||
"authToInitiateSignIn": "Yedekleme için giriş yapmayı başlatmak üzere lütfen kimlik doğrulaması yapın.",
|
||||
"ok": "Tamam",
|
||||
"cancel": "İptal Et",
|
||||
"yes": "Evet",
|
||||
"no": "Hayır",
|
||||
"email": "E-Posta",
|
||||
"support": "Destek",
|
||||
"general": "Genel",
|
||||
"settings": "Ayarlar",
|
||||
"copied": "Kopyalandı",
|
||||
"pleaseTryAgain": "Lütfen tekrar deneyin",
|
||||
@@ -179,6 +189,7 @@
|
||||
"enterDetailsManually": "Bilgileri elle girin",
|
||||
"edit": "Düzenle",
|
||||
"copiedToClipboard": "Panoya kopyalandı",
|
||||
"copiedNextToClipboard": "Sonraki kod panoya kopyalandı",
|
||||
"error": "Hata",
|
||||
"recoveryKeyCopiedToClipboard": "Kurtarma anahtarı panoya kopyalandı",
|
||||
"recoveryKeyOnForgotPassword": "Eğer şifrenizi unutursanız, verilerinizi kurtarabileceğiniz tek yol bu anahtardır.",
|
||||
@@ -249,6 +260,10 @@
|
||||
"privacy": "Gizlilik",
|
||||
"terms": "Şartlar",
|
||||
"checkForUpdates": "Güncellemeleri denetleyin",
|
||||
"downloadUpdate": "İndir",
|
||||
"criticalUpdateAvailable": "Kritik güncelleme mevcut",
|
||||
"updateAvailable": "Güncelleme mevcut",
|
||||
"update": "Güncelle",
|
||||
"checking": "Denetleniyor...",
|
||||
"youAreOnTheLatestVersion": "En son sürümdesiniz",
|
||||
"warning": "Uyarı",
|
||||
@@ -313,7 +328,80 @@
|
||||
"plainText": "Düz metin",
|
||||
"passwordToEncryptExport": "Dışa aktarımı şifrelemek için parola",
|
||||
"export": "Dışa aktar",
|
||||
"useOffline": "Yedekleme olmadan kullan",
|
||||
"signInToBackup": "Kodlarınızı yedeklemek için giriş yapın",
|
||||
"singIn": "Giriş yap",
|
||||
"sigInBackupReminder": "Geri yükleyebileceğiniz bir yedeğiniz olduğundan emin olmak için lütfen kodlarınızı dışa aktarın.",
|
||||
"offlineModeWarning": "Yedekleme yapmadan devam etmeyi seçtiniz. Kodlarınızın güvende olduğundan emin olmak için lütfen manuel yedekleme yapın.",
|
||||
"showLargeIcons": "Büyük simgeler göster",
|
||||
"shouldHideCode": "Kodları gizle",
|
||||
"doubleTapToViewHiddenCode": "Kodu görüntülemek için bir girdiye çift dokunabilirsiniz",
|
||||
"focusOnSearchBar": "Uygulama başladığında arama bölümüne odaklan",
|
||||
"confirmUpdatingkey": "Gizli anahtarı güncellemek istediğinizden emin misiniz?",
|
||||
"minimizeAppOnCopy": "Kopyalarken uygulamayı küçült",
|
||||
"editCodeAuthMessage": "Kodu düzenlemek için doğrulama yapın",
|
||||
"deleteCodeAuthMessage": "Kodu silmek için doğrulama yapın",
|
||||
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın"
|
||||
"showQRAuthMessage": "QR kodunu göstermek için doğrulama yapın",
|
||||
"confirmAccountDeleteTitle": "Hesap silme işlemini onayla",
|
||||
"confirmAccountDeleteMessage": "Bu hesap, eğer kullanıyorsanız, diğer ente uygulamalarıyla bağlantılıdır.\n\nTüm ente uygulamalarında yüklediğiniz veriler silinmek üzere programlanacak ve hesabınız kalıcı olarak silinecektir.",
|
||||
"androidBiometricHint": "Kimliği doğrula",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Tanınmadı. Tekrar deneyin.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Başarılı",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "İptal et",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Kimlik doğrulaması gerekli",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Biyometrik gerekli",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Cihaz kimlik bilgileri gerekli",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Cihaz kimlik bilgileri gerekmekte",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Ayarlara git",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Biyometrik kimlik doğrulama cihazınızda ayarlanmamış. Biyometrik kimlik doğrulama eklemek için 'Ayarlar > Güvenlik' bölümüne gidin.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Biyometrik kimlik doğrulama devre dışı. Etkinleştirmek için lütfen ekranınızı kilitleyin ve kilidini açın.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Cihazınızda biyometrik kimlik doğrulama ayarlanmamış. Lütfen telefonunuzda Touch ID veya Face ID'yi etkinleştirin.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "Tamam",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "İnternet bağlantısı yok",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin.",
|
||||
"signOutFromOtherDevices": "Diğer cihazlardan çıkış yap",
|
||||
"signOutOtherBody": "Eğer başka birisinin parolanızı bildiğini düşünüyorsanız, diğer tüm cihazları hesabınızdan çıkışa zorlayabilirsiniz.",
|
||||
"signOutOtherDevices": "Diğer cihazlardan çıkış yap",
|
||||
"doNotSignOut": "Çıkış yapma",
|
||||
"hearUsWhereTitle": "Ente'yi nereden duydunuz? (opsiyonel)",
|
||||
"hearUsExplanation": "Biz uygulama kurulumlarını takip etmiyoruz. Bizi nereden duyduğunuzdan bahsetmeniz bize çok yardımcı olacak!"
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
{
|
||||
"account": "Tài khoản",
|
||||
"unlock": "Mở khóa",
|
||||
"recoveryKey": "Khóa khôi phục",
|
||||
"counterAppBarTitle": "Bộ Đếm",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "Bảo mật mã 2FA của bạn",
|
||||
"onBoardingBody": "Sao lưu an toàn mã 2FA của bạn",
|
||||
"onBoardingGetStarted": "Bắt đầu",
|
||||
"setupFirstAccount": "Thiết lập tài khoản đầu tiên của bạn",
|
||||
"importScanQrCode": "Quét mã QR",
|
||||
"qrCode": "Mã QR",
|
||||
"importEnterSetupKey": "Nhập khóa thiết lập",
|
||||
"importAccountPageTitle": "Nhập chi tiết tài khoản",
|
||||
"secretCanNotBeEmpty": "Khoá bí mật không được để trống",
|
||||
@@ -46,7 +48,7 @@
|
||||
"copyEmailAction": "Sao chép email",
|
||||
"exportLogsAction": "Xuất nhật ký",
|
||||
"reportABug": "Báo cáo lỗi",
|
||||
"crashAndErrorReporting": "Báo cáo sự cố và lỗi",
|
||||
"crashAndErrorReporting": "Báo cáo sự cố & lỗi",
|
||||
"reportBug": "Báo lỗi",
|
||||
"emailUsMessage": "Vui lòng gửi email cho chúng tôi tại {email}",
|
||||
"@emailUsMessage": {
|
||||
@@ -57,11 +59,14 @@
|
||||
}
|
||||
},
|
||||
"contactSupport": "Liên hệ hỗ trợ",
|
||||
"rateUsOnStore": "Đánh giá chúng tôi trên {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Hàng hóa",
|
||||
"verifyPassword": "Xác nhận mật khẩu",
|
||||
"pleaseWait": "Vui lòng chờ...",
|
||||
"generatingEncryptionKeysTitle": "Đang tạo khóa mã hóa...",
|
||||
"recreatePassword": "Tạo lại mật khẩu",
|
||||
"recreatePasswordMessage": "Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn, vì vậy chúng tôi cần tạo lại mật khẩu một lần theo cách hoạt động với tất cả các thiết bị. \n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).",
|
||||
"recreatePasswordMessage": "Thiết bị hiện tại không đủ mạnh để xác minh mật khẩu của bạn, vì vậy chúng tôi cần tạo lại mật khẩu một lần theo cách hoạt động với tất cả các thiết bị.\n\nVui lòng đăng nhập bằng khóa khôi phục và tạo lại mật khẩu của bạn (bạn có thể sử dụng lại cùng một mật khẩu nếu muốn).",
|
||||
"useRecoveryKey": "Dùng khóa khôi phục",
|
||||
"incorrectPasswordTitle": "Mật khẩu không đúng",
|
||||
"welcomeBack": "Chào mừng trở lại!",
|
||||
@@ -72,19 +77,39 @@
|
||||
"changePassword": "Thay đổi mật khẩu",
|
||||
"data": "Dữ liệu",
|
||||
"importCodes": "Nhập mã",
|
||||
"importTypePlainText": "Văn bản thuần",
|
||||
"importTypeEnteEncrypted": "xuất ente đã mã hóa",
|
||||
"passwordForDecryptingExport": "Mật khẩu để giải mã xuất",
|
||||
"passwordEmptyError": "Mật khẩu không thể để trống",
|
||||
"importFromApp": "Nhập mã từ {appName}",
|
||||
"importGoogleAuthGuide": "Xuất dữ liệu tài khoản của bạn từ Google Authenticator sang mã QR bằng tùy chọn \"Chuyển tài khoản\". Sau đó dùng thiết bị khác quét mã QR.",
|
||||
"importSelectJsonFile": "Chọn tệp JSON",
|
||||
"importSelectAppExport": "Chọn {appName} tệp dữ liệu xuất",
|
||||
"importEnteEncGuide": "Chọn tệp JSON được mã hóa đã xuất từ ente",
|
||||
"importRaivoGuide": "Sử dụng tùy chọn \"Xuất OTP sang lưu trữ Zip\" trong cài đặt của Raivo.",
|
||||
"importBitwardenGuide": "Sử dụng tùy chọn \"Xuất vault\" trong công cụ Bitwarden và nhập tệp JSON không được mã hóa.",
|
||||
"importAegisGuide": "Nếu vault của bạn được mã hóa, bạn sẽ cần nhập mật khẩu vault để giải mã vault.",
|
||||
"import2FasGuide": "Sử dụng tùy chọn \"Cài đặt->Sao lưu -Xuất dữ liệu\" trong 2FAS.\n\nNếu bản sao lưu của bạn được mã hóa, bạn sẽ cần nhập mật khẩu để giải mã bản sao lưu",
|
||||
"exportCodes": "Xuất mã",
|
||||
"importLabel": "Nhập",
|
||||
"importInstruction": "Vui lòng chọn tệp chứa danh sách mã của bạn ở định dạng sau",
|
||||
"importCodeDelimiterInfo": "Các mã có thể được phân tách bằng một dấu phẩy hoặc một dòng mới",
|
||||
"selectFile": "Chọn tập tin",
|
||||
"emailVerificationToggle": "Email xác thực",
|
||||
"emailVerificationEnableWarning": "Để tránh bị khóa tài khoản, hãy đảm bảo lưu trữ bản sao email 2FA của bạn bên ngoài Ente Auth trước khi bật xác minh email.",
|
||||
"authToChangeEmailVerificationSetting": "Vui lòng xác thực để thay đổi email",
|
||||
"authToViewYourRecoveryKey": "Vui lòng xác thực để xem khóa khôi phục của bạn",
|
||||
"authToChangeYourEmail": "Vui lòng xác thực để thay đổi email của bạn",
|
||||
"authToChangeYourPassword": "Vui lòng xác thực để thay đổi mật khẩu của bạn",
|
||||
"ok": "Được rồi",
|
||||
"authToViewSecrets": "Vui lòng xác thực để xem bí mật của bạn",
|
||||
"authToInitiateSignIn": "Vui lòng xác thực để bắt đầu đăng nhập nhằm sao lưu.",
|
||||
"ok": "Đồng ý",
|
||||
"cancel": "Hủy",
|
||||
"yes": "Đúng",
|
||||
"no": "Không",
|
||||
"email": "Thư điện tử",
|
||||
"support": "Hỗ trợ",
|
||||
"general": "Tổng quan",
|
||||
"settings": "Cài đặt",
|
||||
"copied": "\u001dĐã sao chép",
|
||||
"pleaseTryAgain": "Vui lòng thử lại",
|
||||
@@ -94,6 +119,18 @@
|
||||
"enterYourPasswordHint": "Nhập mật khẩu của bạn",
|
||||
"forgotPassword": "Quên mật khẩu",
|
||||
"oops": "Rất tiếc",
|
||||
"suggestFeatures": "Tính năng đề nghị",
|
||||
"faq": "Câu hỏi thường gặp",
|
||||
"faq_q_1": "Mức độ an toàn của ente Auth như thế nào?",
|
||||
"faq_a_1": "Tất cả các mã bạn sao lưu qua ente đều được lưu trữ dưới dạng mã hóa đầu cuối. Điều này có nghĩa là chỉ bạn mới có thể truy cập mã của mình. Ứng dụng của chúng tôi là nguồn mở và mật mã của chúng tôi đã được kiểm toán độc lập.",
|
||||
"faq_q_2": "Tôi có thể truy cập mã của mình trên máy tính không?",
|
||||
"faq_a_2": "Bạn có thể truy cập mã của mình trên web @ auth.ente.io.",
|
||||
"faq_q_3": "Làm cách nào để xóa mã?",
|
||||
"faq_a_3": "Bạn có thể xóa mã bằng cách vuốt sang trái vào mục đó.",
|
||||
"faq_q_4": "Tôi có thể hỗ trợ dự án này như thế nào?",
|
||||
"faq_a_4": "Bạn có thể hỗ trợ sự phát triển của dự án này bằng cách đăng ký ứng dụng Ảnh @ ente.io của chúng tôi.",
|
||||
"faq_q_5": "Tôi có thể bật khóa FaceID trong ente Auth như thế nào",
|
||||
"faq_a_5": "Bạn có thể bật khóa FaceID trong Cài đặt → Bảo mật → Màn hình khóa.",
|
||||
"somethingWentWrongMessage": "Phát hiện có lỗi, xin thử lại",
|
||||
"leaveFamily": "Rời khỏi gia đình",
|
||||
"leaveFamilyMessage": "Bạn có chắc chắn muốn thoát khỏi gói dành cho gia đình không?",
|
||||
@@ -152,12 +189,14 @@
|
||||
"enterDetailsManually": "Nhập chi tiết thủ công",
|
||||
"edit": "Sửa",
|
||||
"copiedToClipboard": "Đã sao chép vào khay nhớ tạm",
|
||||
"copiedNextToClipboard": "Đã sao chép mã tiếp theo vào bảng nhớ tạm",
|
||||
"error": "Lỗi",
|
||||
"recoveryKeyCopiedToClipboard": "Đã sao chép khóa khôi phục vào bộ nhớ tạm",
|
||||
"recoveryKeyOnForgotPassword": "Nếu bạn quên mật khẩu, cách duy nhất bạn có thể khôi phục dữ liệu của mình là sử dụng khóa này.",
|
||||
"recoveryKeySaveDescription": "Chúng tôi không lưu trữ khóa này, vui lòng lưu khóa 24 từ này ở nơi an toàn.",
|
||||
"doThisLater": "Để sau",
|
||||
"saveKey": "Lưu khóa",
|
||||
"back": "Quay lại",
|
||||
"createAccount": "Tạo tài khoản",
|
||||
"passwordStrength": "Độ mạnh mật khẩu: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
@@ -221,6 +260,10 @@
|
||||
"privacy": "Riêng tư",
|
||||
"terms": "Điều khoản",
|
||||
"checkForUpdates": "Kiểm tra cập nhật",
|
||||
"downloadUpdate": "Tải xuống",
|
||||
"criticalUpdateAvailable": "Đã có bản cập nhật quan trọng",
|
||||
"updateAvailable": "Đã có bản cập nhật",
|
||||
"update": "Cập nhật",
|
||||
"checking": "Đang kiểm tra...",
|
||||
"youAreOnTheLatestVersion": "Bạn đang sử dụng phiên bản mới nhất",
|
||||
"warning": "Cánh báo",
|
||||
@@ -266,7 +309,99 @@
|
||||
"terminateSession": "Chấm dứt phiên?",
|
||||
"terminate": "Dừng lại",
|
||||
"thisDevice": "Thiết bị này",
|
||||
"toResetVerifyEmail": "Để đặt lại mật khẩu, vui lòng xác minh email của bạn trước.",
|
||||
"thisEmailIsAlreadyInUse": "Email này đã được sử dụng",
|
||||
"verificationFailedPleaseTryAgain": "Mã xác nhận thất bại. Vui lòng thử lại",
|
||||
"yourVerificationCodeHasExpired": "Mã xác minh của bạn đã hết hạn",
|
||||
"incorrectCode": "Mã không chính xác",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Xin lỗi, mã bạn đã nhập không chính xác",
|
||||
"emailChangedTo": "Thay đổi email thành {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Xác thực lỗi, vui lòng thử lại",
|
||||
"authenticationSuccessful": "Xác thực thành công!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Xác thực hai bước được khôi phục thành công",
|
||||
"incorrectRecoveryKey": "Khóa khôi phục không chính xác",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "Khóa khôi phục bạn đã nhập không chính xác",
|
||||
"enterPassword": "Nhập mật khẩu",
|
||||
"selectExportFormat": "Chọn định dạng dữ liệu xuất",
|
||||
"exportDialogDesc": "Xuất dữ liệu được mã hóa sẽ được bảo vệ bằng mật khẩu bạn chọn.",
|
||||
"encrypted": "Đã mã hóa",
|
||||
"plainText": "Văn bản thuần",
|
||||
"passwordToEncryptExport": "Mật khẩu để giải mã dữ liệu xuất",
|
||||
"export": "Xuất dữ liệu",
|
||||
"useOffline": "Sử dụng mà không sao lưu",
|
||||
"signInToBackup": "Đăng nhập để sao lưu mã của bạn",
|
||||
"singIn": "Đăng nhập",
|
||||
"sigInBackupReminder": "Vui lòng xuất mã của bạn để đảm bảo rằng bạn có bản sao lưu có thể khôi phục.",
|
||||
"offlineModeWarning": "Bạn đã chọn tiếp tục mà không cần sao lưu. Vui lòng sao lưu thủ công để đảm bảo mã của bạn được an toàn.",
|
||||
"showLargeIcons": "Hiển thị biểu tượng lớn",
|
||||
"shouldHideCode": "Ẩn mã",
|
||||
"doubleTapToViewHiddenCode": "Bạn có thể nhấn đúp vào một mục để xem mã",
|
||||
"focusOnSearchBar": "Mở tìm kiếm khi khởi động ứng dụng",
|
||||
"confirmUpdatingkey": "Bạn có chắc chắn muốn cập nhật khóa bí mật không?",
|
||||
"minimizeAppOnCopy": "Thu nhỏ khi sao chép",
|
||||
"editCodeAuthMessage": "Xác minh để chỉnh sửa mã",
|
||||
"deleteCodeAuthMessage": "Xác minh để xóa mã",
|
||||
"showQRAuthMessage": "Xác minh để hiển thị mã QR"
|
||||
"showQRAuthMessage": "Xác minh để hiển thị mã QR",
|
||||
"confirmAccountDeleteTitle": "Xác nhận xóa tài khoản",
|
||||
"confirmAccountDeleteMessage": "Tài khoản này được liên kết với các ứng dụng ente khác, nếu bạn sử dụng bất kỳ ứng dụng nào.\n\nDữ liệu đã tải lên của bạn, trên tất cả các ứng dụng, sẽ bị lên lịch xóa và tài khoản của bạn sẽ bị xóa vĩnh viễn.",
|
||||
"androidBiometricHint": "Xác định danh tính",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Không nhận dạng được. Vui lòng thử lại.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Thành công",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Hủy",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Yêu cầu xác thực",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Yêu cầu sinh trắc học",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Yêu cầu thông tin xác thực thiết bị",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Yêu cầu thông tin xác thực thiết bị",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Chuyển đến cài đặt",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Đi tới 'Cài đặt > Bảo mật' để thêm xác thực sinh trắc học.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Xác thực sinh trắc học bị vô hiệu hóa. Vui lòng khóa và mở khóa màn hình của bạn để kích hoạt nó.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Xác thực sinh trắc học chưa được thiết lập trên thiết bị của bạn. Vui lòng bật Touch ID hoặc Face ID trên điện thoại của bạn.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "Đồng ý",
|
||||
"@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."
|
||||
},
|
||||
"noInternetConnection": "Không có kết nối Internet",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Vui lòng kiểm tra kết nối internet của bạn và thử lại.",
|
||||
"signOutFromOtherDevices": "Đăng xuất khỏi các thiết bị khác",
|
||||
"signOutOtherBody": "Nếu bạn cho rằng ai đó có thể biết mật khẩu của mình, bạn có thể buộc đăng xuất tất cả các thiết bị khác đang sử dụng tài khoản của mình.",
|
||||
"signOutOtherDevices": "Đăng xuất khỏi các thiết bị khác",
|
||||
"doNotSignOut": "Không được đăng xuất",
|
||||
"hearUsWhereTitle": "Bạn biết đến Ente bằng cách nào? (không bắt buộc)",
|
||||
"hearUsExplanation": "Chúng tôi không theo dõi lượt cài đặt ứng dụng. Sẽ rất hữu ích nếu bạn cho chúng tôi biết nơi bạn tìm thấy chúng tôi!"
|
||||
}
|
||||
@@ -84,10 +84,12 @@
|
||||
"importFromApp": "从 {appName} 导入代码",
|
||||
"importGoogleAuthGuide": "使用“转移帐户”选项将您的帐户从 Google 身份验证器导出到二维码。然后使用另一台设备扫描二维码。\n\n提示:您可以使用笔记本电脑的网络摄像头拍摄二维码的照片。",
|
||||
"importSelectJsonFile": "选择 JSON 文件",
|
||||
"importSelectAppExport": "选择 {appName} 的导出文件",
|
||||
"importEnteEncGuide": "选择从ente导出的加密JSON文件",
|
||||
"importRaivoGuide": "使用 Raivo 设置中的“将 OTP 导出到 Zip 存档”选项。\n\n解压 zip 文件并导入 JSON 文件。",
|
||||
"importBitwardenGuide": "使用 Bitwarden 工具中的“导出保管库”选项并导入未加密的 JSON 文件。",
|
||||
"importAegisGuide": "在Aegis的设置中使用\"导出密码库\"选项。\n\n如果您的密码库已加密,您需要输入密码才能解密密码库。",
|
||||
"import2FasGuide": "使用 2FAS 中的“设置 -> 备份 - 导出”选项。\n\n如果您的备份已被加密,则需要输入密码才能解密备份",
|
||||
"exportCodes": "导出代码",
|
||||
"importLabel": "导入",
|
||||
"importInstruction": "请以以下格式选择包含代码列表的文件",
|
||||
@@ -100,6 +102,7 @@
|
||||
"authToChangeYourEmail": "请验证以更改您的电子邮件",
|
||||
"authToChangeYourPassword": "请验证以更改密码",
|
||||
"authToViewSecrets": "请进行身份验证以查看您的秘密",
|
||||
"authToInitiateSignIn": "请进行身份验证以启动登录进行备份。",
|
||||
"ok": "好的",
|
||||
"cancel": "取消",
|
||||
"yes": "是",
|
||||
@@ -332,6 +335,7 @@
|
||||
"offlineModeWarning": "您已选择在不进行备份的情况下继续操作。请手动备份以确保您的代码安全。",
|
||||
"showLargeIcons": "显示大图标",
|
||||
"shouldHideCode": "隐藏代码",
|
||||
"doubleTapToViewHiddenCode": "您可以双击条目来查看代码",
|
||||
"focusOnSearchBar": "应用启动后聚焦搜索",
|
||||
"confirmUpdatingkey": "您确定要更新此密钥吗?",
|
||||
"minimizeAppOnCopy": "复制时最小化应用",
|
||||
@@ -340,13 +344,6 @@
|
||||
"showQRAuthMessage": "显示QR码需要身份验证",
|
||||
"confirmAccountDeleteTitle": "确认删除账户",
|
||||
"confirmAccountDeleteMessage": "该账户已链接到其他ente旗下的应用程序(如果您使用任何相关的应用程序)。\n\n您在所有ente旗下应用程序中上传的数据都将被安排删除,并且您的账户将被永久删除。",
|
||||
"reminderText": "提醒",
|
||||
"reminderPopupBody": "请先删除屏幕截图,然后再恢复任何照片的云同步",
|
||||
"invalidQrCodeText": "二维码无效",
|
||||
"googleAuthImagePopupBody": "请关闭所有应用程序中的所有照片云同步,包括 iCloud、Google Photo、OneDrive 等。 \n此外,如果您有第二部智能手机,通过扫描二维码导入会更安全。",
|
||||
"importGoogleAuthImageButtonText": "从图像导入",
|
||||
"unableToRecognizeQrCodeText": "无法从上传的图像中识别有效代码",
|
||||
"qrCodeImageNotSelectedText": "未选择二维码图像",
|
||||
"androidBiometricHint": "验证身份",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
@@ -399,5 +396,12 @@
|
||||
"@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."
|
||||
},
|
||||
"parsingErrorText": "解析谷歌验证器的二维码时出错"
|
||||
"noInternetConnection": "无互联网连接",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。",
|
||||
"signOutFromOtherDevices": "从其他设备退出登录",
|
||||
"signOutOtherBody": "如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。",
|
||||
"signOutOtherDevices": "登出其他设备",
|
||||
"doNotSignOut": "不要退登",
|
||||
"hearUsWhereTitle": "您是如何知道Ente的? (可选的)",
|
||||
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:computer/computer.dart';
|
||||
import "package:ente_auth/app/view/app.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
@@ -18,23 +19,41 @@ import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/ui/tools/lock_screen.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/window_protocol_handler.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:privacy_screen/privacy_screen.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
final _logger = Logger("main");
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
final savedThemeMode = await AdaptiveTheme.getThemeMode();
|
||||
await _runInForeground(savedThemeMode);
|
||||
FlutterDisplayMode.setHighRefreshRate();
|
||||
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
if (PlatformUtil.isDesktop()) {
|
||||
WindowOptions windowOptions = const WindowOptions(
|
||||
size: Size(450, 800),
|
||||
);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
}
|
||||
await _runInForeground();
|
||||
await _setupPrivacyScreen();
|
||||
if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate();
|
||||
}
|
||||
|
||||
Future<void> _runInForeground(AdaptiveThemeMode? savedThemeMode) async {
|
||||
Future<void> _runInForeground() async {
|
||||
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
|
||||
return await _runWithLogs(() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
await _init(false, via: 'mainMethod');
|
||||
@@ -48,7 +67,7 @@ Future<void> _runInForeground(AdaptiveThemeMode? savedThemeMode) async {
|
||||
locale: locale,
|
||||
lightTheme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
savedThemeMode: _themeMode(savedThemeMode),
|
||||
savedThemeMode: savedThemeMode,
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -62,10 +81,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
|
||||
}
|
||||
|
||||
Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
String dir = "";
|
||||
try {
|
||||
dir = "${(await getApplicationSupportDirectory()).path}/logs";
|
||||
} catch (_) {}
|
||||
await SuperLogging.main(
|
||||
LogConfig(
|
||||
body: function,
|
||||
logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
|
||||
logDirPath: dir,
|
||||
maxLogFiles: 5,
|
||||
sentryDsn: sentryDSN,
|
||||
enableInDebugMode: true,
|
||||
@@ -74,10 +97,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
|
||||
);
|
||||
}
|
||||
|
||||
void _registerWindowsProtocol() {
|
||||
const kWindowsScheme = 'ente';
|
||||
// Register our protocol only on Windows platform
|
||||
if (!kIsWeb && Platform.isWindows) {
|
||||
WindowsProtocolHandler()
|
||||
.register(kWindowsScheme, executable: null, arguments: null);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _init(bool bool, {String? via}) async {
|
||||
// Start workers asynchronously. No need to wait for them to start
|
||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode);
|
||||
CryptoUtil.init();
|
||||
_registerWindowsProtocol();
|
||||
await initCryptoUtil();
|
||||
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await Configuration.instance.init();
|
||||
@@ -90,3 +122,23 @@ Future<void> _init(bool bool, {String? via}) async {
|
||||
await UpdateService.instance.init();
|
||||
await IconUtils.instance.init();
|
||||
}
|
||||
|
||||
Future<void> _setupPrivacyScreen() async {
|
||||
if (!PlatformUtil.isMobile()) return;
|
||||
final brightness =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
bool isInDarkMode = brightness == Brightness.dark;
|
||||
await PrivacyScreen.instance.enable(
|
||||
iosOptions: const PrivacyIosOptions(
|
||||
enablePrivacy: true,
|
||||
privacyImageName: "LaunchImage",
|
||||
lockTrigger: IosLockTrigger.didEnterBackground,
|
||||
),
|
||||
androidOptions: const PrivacyAndroidOptions(
|
||||
enableSecure: true,
|
||||
),
|
||||
backgroundColor: isInDarkMode ? Colors.black : Colors.white,
|
||||
blurEffect:
|
||||
isInDarkMode ? PrivacyBlurEffect.dark : PrivacyBlurEffect.extraLight,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,14 +57,7 @@ class Code {
|
||||
updatedAlgo,
|
||||
updatedType,
|
||||
updatedCounter,
|
||||
"otpauth://${updatedType.name}/" +
|
||||
updateIssuer +
|
||||
":" +
|
||||
updateAccount +
|
||||
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
|
||||
updateIssuer +
|
||||
"&period=$updatePeriod&secret=" +
|
||||
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
|
||||
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
|
||||
generatedID: generatedID,
|
||||
);
|
||||
}
|
||||
@@ -83,14 +76,7 @@ class Code {
|
||||
Algorithm.sha1,
|
||||
Type.totp,
|
||||
0,
|
||||
"otpauth://totp/" +
|
||||
issuer +
|
||||
":" +
|
||||
account +
|
||||
"?algorithm=SHA1&digits=6&issuer=" +
|
||||
issuer +
|
||||
"&period=30&secret=" +
|
||||
secret,
|
||||
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class DerivedKeyResult {
|
||||
final Uint8List key;
|
||||
final int memLimit;
|
||||
final int opsLimit;
|
||||
|
||||
DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
class EncryptionResult {
|
||||
final Uint8List? encryptedData;
|
||||
final Uint8List? key;
|
||||
final Uint8List? header;
|
||||
final Uint8List? nonce;
|
||||
|
||||
EncryptionResult({
|
||||
this.encryptedData,
|
||||
this.key,
|
||||
this.header,
|
||||
this.nonce,
|
||||
});
|
||||
}
|
||||
@@ -7,26 +7,18 @@ import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
import 'package:ente_auth/ui/account/email_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/login_page.dart';
|
||||
import 'package:ente_auth/ui/account/logout_dialog.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_reentry_page.dart';
|
||||
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/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({Key? key}) : super(key: key);
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@override
|
||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||
@@ -56,14 +48,16 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: SingleChildScrollView(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
const BoxConstraints.tightFor(height: 800, width: 450),
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 40.0,
|
||||
horizontal: 40,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Column(
|
||||
@@ -126,18 +120,20 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
// TODO: Remove After Stable
|
||||
// Container(
|
||||
// width: double.infinity,
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
// child: GradientButton(
|
||||
// onTap: _navigateToSignUpPage,
|
||||
// text: l10n.newUser,
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 24),
|
||||
Container(
|
||||
height: 56,
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: GradientButton(
|
||||
onTap: _navigateToSignUpPage,
|
||||
text: l10n.newUser,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
@@ -155,22 +151,23 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
child: GestureDetector(
|
||||
onTap: _optForOfflineMode,
|
||||
child: Center(
|
||||
child: Text(
|
||||
l10n.useOffline,
|
||||
style: body.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.mutedTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// TODO: Remove After Stable
|
||||
// Container(
|
||||
// width: double.infinity,
|
||||
// padding: const EdgeInsets.only(top: 20, bottom: 20),
|
||||
// child: GestureDetector(
|
||||
// onTap: _optForOfflineMode,
|
||||
// child: Center(
|
||||
// child: Text(
|
||||
// l10n.useOffline,
|
||||
// style: body.copyWith(
|
||||
// color:
|
||||
// Theme.of(context).colorScheme.mutedTextColor,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -181,63 +178,69 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _optForOfflineMode() async {
|
||||
bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
|
||||
if(!canCheckBio) {
|
||||
showToast(context, "Sorry, biometric authentication is not supported on this device.");
|
||||
return;
|
||||
}
|
||||
final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode();
|
||||
ButtonResult? result;
|
||||
if(!hasOptedBefore) {
|
||||
result = await showChoiceActionSheet(
|
||||
context,
|
||||
title: context.l10n.warning,
|
||||
body: context.l10n.offlineModeWarning,
|
||||
secondButtonLabel: context.l10n.cancel,
|
||||
firstButtonLabel: context.l10n.ok,
|
||||
);
|
||||
}
|
||||
if (hasOptedBefore || result?.action == ButtonAction.first) {
|
||||
await Configuration.instance.optForOfflineMode();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
// TODO: Remove After Stable
|
||||
// Future<void> _optForOfflineMode() async {
|
||||
// final canContinue = Platform.isMacOS || Platform.isLinux
|
||||
// ? true
|
||||
// : await LocalAuthentication().canCheckBiometrics;
|
||||
|
||||
}
|
||||
// if (!canContinue) {
|
||||
// showToast(
|
||||
// context,
|
||||
// "Sorry, biometric authentication is not supported on this device.",
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode();
|
||||
// ButtonResult? result;
|
||||
// if (!hasOptedBefore) {
|
||||
// result = await showChoiceActionSheet(
|
||||
// context,
|
||||
// title: context.l10n.warning,
|
||||
// body: context.l10n.offlineModeWarning,
|
||||
// secondButtonLabel: context.l10n.cancel,
|
||||
// firstButtonLabel: context.l10n.ok,
|
||||
// );
|
||||
// }
|
||||
// if (hasOptedBefore || result?.action == ButtonAction.first) {
|
||||
// await Configuration.instance.optForOfflineMode();
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (BuildContext context) {
|
||||
// return const HomePage();
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
void _navigateToSignUpPage() {
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() == null) {
|
||||
page = const EmailEntryPage();
|
||||
} else {
|
||||
// No key
|
||||
if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// Never had a key
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
);
|
||||
} else if (Configuration.instance.getKey() == null) {
|
||||
// Yet to decrypt the key
|
||||
page = const PasswordReentryPage();
|
||||
} else {
|
||||
// All is well, user just has not subscribed
|
||||
page = const HomePage();
|
||||
}
|
||||
}
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
// void _navigateToSignUpPage() {
|
||||
// Widget page;
|
||||
// if (Configuration.instance.getEncryptedToken() == null) {
|
||||
// page = const EmailEntryPage();
|
||||
// } else {
|
||||
// // No key
|
||||
// if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// // Never had a key
|
||||
// page = const PasswordEntryPage(
|
||||
// mode: PasswordEntryMode.set,
|
||||
// );
|
||||
// } else if (Configuration.instance.getKey() == null) {
|
||||
// // Yet to decrypt the key
|
||||
// page = const PasswordReentryPage();
|
||||
// } else {
|
||||
// // All is well, user just has not subscribed
|
||||
// page = const HomePage();
|
||||
// }
|
||||
// }
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(
|
||||
// builder: (BuildContext context) {
|
||||
// return page;
|
||||
// },
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
void _navigateToSignInPage() {
|
||||
Widget page;
|
||||
|
||||
@@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
||||
class SetupEnterSecretKeyPage extends StatefulWidget {
|
||||
final Code? code;
|
||||
|
||||
SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
|
||||
SetupEnterSecretKeyPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
State<SetupEnterSecretKeyPage> createState() =>
|
||||
@@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
||||
);
|
||||
_secretController = TextEditingController(
|
||||
text: widget.code != null ? widget.code!.secret : null,
|
||||
text: widget.code?.secret,
|
||||
);
|
||||
_secretKeyObscured = widget.code != null;
|
||||
super.initState();
|
||||
@@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.importAccountPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
@@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
||||
class ViewQrPage extends StatelessWidget {
|
||||
final Code? code;
|
||||
|
||||
ViewQrPage({this.code, Key? key}) : super(key: key);
|
||||
ViewQrPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.qrCode),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
QrImage(
|
||||
QrImageView(
|
||||
data: code!.rawData,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
version: QrVersions.auto,
|
||||
size: qrSize,
|
||||
),
|
||||
|
||||
@@ -16,9 +16,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -26,6 +25,7 @@ enum AccountMode {
|
||||
online,
|
||||
offline,
|
||||
}
|
||||
|
||||
extension on AccountMode {
|
||||
bool get isOnline => this == AccountMode.online;
|
||||
bool get isOffline => this == AccountMode.offline;
|
||||
@@ -75,10 +75,10 @@ class AuthenticatorService {
|
||||
final key = await getOrCreateAuthDataKey(mode);
|
||||
for (LocalAuthEntity e in result) {
|
||||
try {
|
||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
||||
Sodium.base642bin(e.encryptedData),
|
||||
final decryptedValue = await CryptoUtil.decryptData(
|
||||
CryptoUtil.base642bin(e.encryptedData),
|
||||
key,
|
||||
Sodium.base642bin(e.header),
|
||||
CryptoUtil.base642bin(e.header),
|
||||
);
|
||||
final hasSynced = !(e.id == null || e.shouldSync);
|
||||
entities.add(
|
||||
@@ -101,12 +101,13 @@ class AuthenticatorService {
|
||||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final insertedID = accountMode.isOnline
|
||||
? await _db.insert(encryptedData, header)
|
||||
: await _offlineDb.insert(encryptedData, header);
|
||||
@@ -123,12 +124,13 @@ class AuthenticatorService {
|
||||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final int affectedRows = accountMode.isOnline
|
||||
? await _db.updateEntry(generatedID, encryptedData, header)
|
||||
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
||||
@@ -154,7 +156,7 @@ class AuthenticatorService {
|
||||
} else {
|
||||
debugPrint("Skipping delete since account mode is offline");
|
||||
}
|
||||
if(accountMode.isOnline) {
|
||||
if (accountMode.isOnline) {
|
||||
await _db.deleteByIDs(generatedIDs: [genID]);
|
||||
} else {
|
||||
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
|
||||
@@ -163,7 +165,7 @@ class AuthenticatorService {
|
||||
|
||||
Future<bool> onlineSync() async {
|
||||
try {
|
||||
if(getAccountMode().isOffline) {
|
||||
if (getAccountMode().isOffline) {
|
||||
debugPrint("Skipping sync since account mode is offline");
|
||||
return false;
|
||||
}
|
||||
@@ -191,25 +193,25 @@ class AuthenticatorService {
|
||||
Future<void> _remoteToLocalSync() async {
|
||||
_logger.info('Initiating remote to local sync');
|
||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||
_logger.info("Current sync is " + lastSyncTime.toString());
|
||||
_logger.info("Current sync is $lastSyncTime");
|
||||
const int fetchLimit = 500;
|
||||
final List<AuthEntity> result =
|
||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
||||
_logger.info(result.length.toString() + " entries fetched from remote");
|
||||
_logger.info("${result.length} entries fetched from remote");
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||
List<String> deletedIDs =
|
||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
||||
_logger.info(deletedIDs.length.toString() + " entries deleted");
|
||||
_logger.info("${deletedIDs.length} entries deleted");
|
||||
result.removeWhere((element) => element.isDeleted);
|
||||
await _db.insertOrReplace(result);
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
await _db.deleteByIDs(ids: deletedIDs);
|
||||
}
|
||||
_prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
||||
_logger.info("Setting synctime to $maxSyncTime");
|
||||
if (result.length == fetchLimit) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
await _remoteToLocalSync();
|
||||
@@ -223,7 +225,7 @@ class AuthenticatorService {
|
||||
.where((element) => element.shouldSync || element.id == null)
|
||||
.toList();
|
||||
_logger.info(
|
||||
pendingUpdate.length.toString() + " entries to be updated at remote",
|
||||
"${pendingUpdate.length} entries to be updated at remote",
|
||||
);
|
||||
for (LocalAuthEntity entity in pendingUpdate) {
|
||||
if (entity.id == null) {
|
||||
@@ -253,7 +255,7 @@ class AuthenticatorService {
|
||||
}
|
||||
|
||||
Future<Uint8List> getOrCreateAuthDataKey(AccountMode mode) async {
|
||||
if(mode.isOffline) {
|
||||
if (mode.isOffline) {
|
||||
return _config.getOfflineSecretKey()!;
|
||||
}
|
||||
if (_config.getAuthSecretKey() != null) {
|
||||
@@ -262,21 +264,21 @@ class AuthenticatorService {
|
||||
try {
|
||||
final AuthKey response = await _gateway.getKey();
|
||||
final authKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(response.encryptedKey),
|
||||
CryptoUtil.base642bin(response.encryptedKey),
|
||||
_config.getKey()!,
|
||||
Sodium.base642bin(response.header),
|
||||
CryptoUtil.base642bin(response.header),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
|
||||
return authKey;
|
||||
} on AuthenticatorKeyNotFound catch (e) {
|
||||
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
||||
final key = CryptoUtil.generateKey();
|
||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||
await _gateway.createKey(
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(key));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
|
||||
return key;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
||||
|
||||
@@ -52,7 +52,7 @@ class BillingService {
|
||||
|
||||
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
||||
return _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/user-plans/",
|
||||
"${_config.getHttpEndpoint()}/billing/user-plans/",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -62,7 +62,7 @@ class BillingService {
|
||||
}
|
||||
|
||||
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
||||
return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
|
||||
return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
|
||||
}
|
||||
|
||||
Future<Subscription> verifySubscription(
|
||||
@@ -72,7 +72,7 @@ class BillingService {
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/verify-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/verify-subscription",
|
||||
data: {
|
||||
"paymentProvider": paymentProvider ??
|
||||
(Platform.isAndroid ? "playstore" : "appstore"),
|
||||
@@ -86,7 +86,7 @@ class BillingService {
|
||||
),
|
||||
);
|
||||
return Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 409) {
|
||||
throw SubscriptionAlreadyClaimedError();
|
||||
} else {
|
||||
@@ -102,7 +102,7 @@ class BillingService {
|
||||
if (_cachedSubscription == null) {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -111,7 +111,7 @@ class BillingService {
|
||||
);
|
||||
_cachedSubscription =
|
||||
Subscription.fromMap(response.data["subscription"]);
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -122,7 +122,7 @@ class BillingService {
|
||||
Future<Subscription> cancelStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -131,7 +131,7 @@ class BillingService {
|
||||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -140,7 +140,7 @@ class BillingService {
|
||||
Future<Subscription> activateStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
@@ -149,7 +149,7 @@ class BillingService {
|
||||
);
|
||||
final subscription = Subscription.fromMap(response.data["subscription"]);
|
||||
return subscription;
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ class BillingService {
|
||||
}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/customer-portal",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
|
||||
queryParameters: {
|
||||
"redirectURL": kWebPaymentRedirectUrl,
|
||||
},
|
||||
@@ -171,7 +171,7 @@ class BillingService {
|
||||
),
|
||||
);
|
||||
return response.data["url"];
|
||||
} on DioError catch (e, s) {
|
||||
} on DioException catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/utils/auth_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_authentication/flutter_local_authentication.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class LocalAuthenticationService {
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
static final LocalAuthenticationService instance =
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
final logger = Logger((LocalAuthenticationService).toString());
|
||||
|
||||
Future<bool> requestLocalAuthentication(
|
||||
BuildContext context,
|
||||
@@ -38,7 +44,7 @@ class LocalAuthenticationService {
|
||||
String errorDialogContent, [
|
||||
String errorDialogTitle = "",
|
||||
]) async {
|
||||
if (await LocalAuthentication().isDeviceSupported()) {
|
||||
if (await _isLocalAuthSupportedOnDevice()) {
|
||||
AppLock.of(context)!.disable();
|
||||
final result = await requestAuthentication(
|
||||
context,
|
||||
@@ -64,6 +70,12 @@ class LocalAuthenticationService {
|
||||
}
|
||||
|
||||
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
||||
return await LocalAuthentication().isDeviceSupported();
|
||||
try {
|
||||
return Platform.isMacOS || Platform.isLinux
|
||||
? await FlutterLocalAuthentication().canAuthenticate()
|
||||
: await LocalAuthentication().isDeviceSupported();
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class NotificationService {
|
||||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
implementation.requestPermission();
|
||||
implementation.requestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ class UserRemoteFlagService {
|
||||
queryParams["defaultValue"] = defaultValue;
|
||||
}
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/remote-store",
|
||||
"${_config.getHttpEndpoint()}/remote-store",
|
||||
queryParameters: queryParams,
|
||||
options: Options(
|
||||
headers: {
|
||||
@@ -119,7 +119,7 @@ class UserRemoteFlagService {
|
||||
Future<void> _updateKeyValue(String key, String value) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/remote-store/update",
|
||||
"${_config.getHttpEndpoint()}/remote-store/update",
|
||||
data: {
|
||||
"key": key,
|
||||
"value": value,
|
||||
|
||||
@@ -24,14 +24,13 @@ import 'package:ente_auth/ui/account/ott_verification_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_reentry_page.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_page.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/common/progress_dialog.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -46,9 +45,10 @@ import "package:uuid/uuid.dart";
|
||||
class UserService {
|
||||
static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
|
||||
static const keyUserDetails = "user_details";
|
||||
static const kReferralSource = "referral_source";
|
||||
static const kCanDisableEmailMFA = "can_disable_email_mfa";
|
||||
static const kIsEmailMFAEnabled = "is_email_mfa_enabled";
|
||||
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
|
||||
final SRP6GroupParameters kDefaultSrpGroup = SRP6StandardGroups.rfc5054_4096;
|
||||
final _dio = Network.instance.getDio();
|
||||
final _enteDio = Network.instance.enteDio;
|
||||
final _logger = Logger((UserService).toString());
|
||||
@@ -68,17 +68,17 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> sendOtt(
|
||||
BuildContext context,
|
||||
String email, {
|
||||
bool isChangeEmail = false,
|
||||
bool isCreateAccountScreen = false,
|
||||
bool isResetPasswordScreen = false,
|
||||
}) async {
|
||||
BuildContext context,
|
||||
String email, {
|
||||
bool isChangeEmail = false,
|
||||
bool isCreateAccountScreen = false,
|
||||
bool isResetPasswordScreen = false,
|
||||
}) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/ott",
|
||||
"${_config.getHttpEndpoint()}/users/ott",
|
||||
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
|
||||
);
|
||||
await dialog.hide();
|
||||
@@ -100,7 +100,7 @@ class UserService {
|
||||
return;
|
||||
}
|
||||
unawaited(showGenericErrorDialog(context: context));
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.info(e);
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
@@ -122,17 +122,16 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> sendFeedback(
|
||||
BuildContext context,
|
||||
String feedback, {
|
||||
String type = "SubCancellation",
|
||||
}) async {
|
||||
BuildContext context,
|
||||
String feedback, {
|
||||
String type = "SubCancellation",
|
||||
}) async {
|
||||
await _dio.post(
|
||||
_config.getHttpEndpoint() + "/anonymous/feedback",
|
||||
"${_config.getHttpEndpoint()}/anonymous/feedback",
|
||||
data: {"feedback": feedback, "type": "type"},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Future<UserDetails> getUserDetailsV2({
|
||||
bool memoryCount = false,
|
||||
bool shouldCache = true,
|
||||
@@ -146,9 +145,15 @@ class UserService {
|
||||
);
|
||||
final userDetails = UserDetails.fromMap(response.data);
|
||||
if (shouldCache) {
|
||||
if(userDetails.profileData != null) {
|
||||
_preferences.setBool(kIsEmailMFAEnabled, userDetails.profileData!.isEmailMFAEnabled);
|
||||
_preferences.setBool(kCanDisableEmailMFA, userDetails.profileData!.canDisableEmailMFA);
|
||||
if (userDetails.profileData != null) {
|
||||
_preferences.setBool(
|
||||
kIsEmailMFAEnabled,
|
||||
userDetails.profileData!.isEmailMFAEnabled,
|
||||
);
|
||||
_preferences.setBool(
|
||||
kCanDisableEmailMFA,
|
||||
userDetails.profileData!.canDisableEmailMFA,
|
||||
);
|
||||
}
|
||||
// handle email change from different client
|
||||
if (userDetails.email != _config.getEmail()) {
|
||||
@@ -156,7 +161,7 @@ class UserService {
|
||||
}
|
||||
}
|
||||
return userDetails;
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
_logger.warning("Failed to fetch", e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -166,7 +171,7 @@ class UserService {
|
||||
try {
|
||||
final response = await _enteDio.get("/users/sessions");
|
||||
return Sessions.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -180,7 +185,7 @@ class UserService {
|
||||
"token": token,
|
||||
},
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -189,7 +194,7 @@ class UserService {
|
||||
Future<void> leaveFamilyPlan() async {
|
||||
try {
|
||||
await _enteDio.delete("/family/leave");
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.warning('failed to leave family plan', e);
|
||||
rethrow;
|
||||
}
|
||||
@@ -210,15 +215,15 @@ class UserService {
|
||||
//to close and only then to show the error dialog.
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 150),
|
||||
() => showGenericErrorDialog(context: context),
|
||||
() => showGenericErrorDialog(context: context),
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<DeleteChallengeResponse?> getDeleteChallenge(
|
||||
BuildContext context,
|
||||
) async {
|
||||
BuildContext context,
|
||||
) async {
|
||||
try {
|
||||
final response = await _enteDio.get("/users/delete-challenge");
|
||||
if (response.statusCode == 200) {
|
||||
@@ -237,8 +242,9 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> deleteAccount(
|
||||
BuildContext context,
|
||||
String challengeResponse,) async {
|
||||
BuildContext context,
|
||||
String challengeResponse,
|
||||
) async {
|
||||
try {
|
||||
final response = await _enteDio.delete(
|
||||
"/users/delete",
|
||||
@@ -258,18 +264,24 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> verifyEmail(BuildContext context, String ott, {bool
|
||||
isResettingPasswordScreen = false,})
|
||||
async {
|
||||
Future<void> verifyEmail(
|
||||
BuildContext context,
|
||||
String ott, {
|
||||
bool isResettingPasswordScreen = false,
|
||||
}) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
final verifyData = {
|
||||
"email": _config.getEmail(),
|
||||
"ott": ott,
|
||||
};
|
||||
if (!_config.isLoggedIn()) {
|
||||
verifyData["source"] = 'auth:${_getRefSource()}';
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/verify-email",
|
||||
data: {
|
||||
"email": _config.getEmail(),
|
||||
"ott": ott,
|
||||
},
|
||||
"${_config.getHttpEndpoint()}/users/verify-email",
|
||||
data: verifyData,
|
||||
);
|
||||
await dialog.hide();
|
||||
if (response.statusCode == 200) {
|
||||
@@ -280,14 +292,15 @@ class UserService {
|
||||
} else {
|
||||
await _saveConfiguration(response);
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
if(isResettingPasswordScreen) {
|
||||
if (isResettingPasswordScreen) {
|
||||
page = const RecoveryPage();
|
||||
} else {
|
||||
page = const PasswordReentryPage();
|
||||
}
|
||||
|
||||
} else {
|
||||
page = const PasswordEntryPage(mode: PasswordEntryMode.set,);
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
);
|
||||
}
|
||||
}
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
@@ -296,13 +309,13 @@ class UserService {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// should never reach here
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.info(e);
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 410) {
|
||||
@@ -336,10 +349,10 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> changeEmail(
|
||||
BuildContext context,
|
||||
String email,
|
||||
String ott,
|
||||
) async {
|
||||
BuildContext context,
|
||||
String email,
|
||||
String ott,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
@@ -363,7 +376,7 @@ class UserService {
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 403) {
|
||||
showErrorDialog(
|
||||
@@ -410,7 +423,7 @@ class UserService {
|
||||
Future<SrpAttributes> getSrpAttributes(String email) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/srp/attributes",
|
||||
"${_config.getHttpEndpoint()}/users/srp/attributes",
|
||||
queryParameters: {
|
||||
"email": email,
|
||||
},
|
||||
@@ -420,7 +433,7 @@ class UserService {
|
||||
} else {
|
||||
throw Exception("get-srp-attributes action failed");
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
throw SrpSetupNotCompleteError();
|
||||
}
|
||||
@@ -431,9 +444,10 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> registerOrUpdateSrp(
|
||||
Uint8List loginKey, {
|
||||
SetKeysRequest? setKeysRequest,
|
||||
}) async {
|
||||
Uint8List loginKey, {
|
||||
SetKeysRequest? setKeysRequest,
|
||||
bool logOutOtherDevices = false,
|
||||
}) async {
|
||||
try {
|
||||
final String username = const Uuid().v4().toString();
|
||||
final SecureRandom random = _getSecureRandom();
|
||||
@@ -466,15 +480,15 @@ class UserService {
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
final SetupSRPResponse setupSRPResponse =
|
||||
SetupSRPResponse.fromJson(response.data);
|
||||
SetupSRPResponse.fromJson(response.data);
|
||||
final serverB =
|
||||
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
|
||||
SRP6Util.decodeBigInt(base64Decode(setupSRPResponse.srpB));
|
||||
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
late Response srpCompleteResponse;
|
||||
if(setKeysRequest == null) {
|
||||
srpCompleteResponse = await _enteDio.post(
|
||||
late Response _;
|
||||
if (setKeysRequest == null) {
|
||||
_ = await _enteDio.post(
|
||||
"/users/srp/complete",
|
||||
data: {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
@@ -482,20 +496,21 @@ class UserService {
|
||||
},
|
||||
);
|
||||
} else {
|
||||
srpCompleteResponse = await _enteDio.post(
|
||||
_ = await _enteDio.post(
|
||||
"/users/srp/update",
|
||||
data: {
|
||||
'setupID': setupSRPResponse.setupID,
|
||||
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
|
||||
'updatedKeyAttr': setKeysRequest.toMap(),
|
||||
'logOutOtherDevices': logOutOtherDevices,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw Exception("register-srp action failed");
|
||||
}
|
||||
} catch (e,s) {
|
||||
_logger.severe("failed to register srp" ,e,s);
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to register srp", e, s);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -512,133 +527,97 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> verifyEmailViaPassword(
|
||||
BuildContext context,
|
||||
SrpAttributes srpAttributes,
|
||||
String userPassword,
|
||||
) async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.l10n.pleaseWait,
|
||||
isDismissible: true,
|
||||
);
|
||||
await dialog.show();
|
||||
BuildContext context,
|
||||
SrpAttributes srpAttributes,
|
||||
String userPassword,
|
||||
ProgressDialog dialog,
|
||||
) async {
|
||||
late Uint8List keyEncryptionKey;
|
||||
try {
|
||||
keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(userPassword) as Uint8List,
|
||||
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
||||
srpAttributes.memLimit,
|
||||
srpAttributes.opsLimit,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
|
||||
final Uint8List identity = Uint8List.fromList(
|
||||
utf8.encode(srpAttributes.srpUserID),
|
||||
);
|
||||
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
|
||||
final Uint8List password = loginKey;
|
||||
final SecureRandom random = _getSecureRandom();
|
||||
_logger.finest('Start deriving key');
|
||||
keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(userPassword),
|
||||
CryptoUtil.base642bin(srpAttributes.kekSalt),
|
||||
srpAttributes.memLimit,
|
||||
srpAttributes.opsLimit,
|
||||
);
|
||||
_logger.finest('keyDerivation done, derive LoginKey');
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
|
||||
final Uint8List identity = Uint8List.fromList(
|
||||
utf8.encode(srpAttributes.srpUserID),
|
||||
);
|
||||
_logger.finest('longinKey derivation done');
|
||||
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
|
||||
final Uint8List password = loginKey;
|
||||
final SecureRandom random = _getSecureRandom();
|
||||
|
||||
final client = SRP6Client(
|
||||
group: kDefaultSrpGroup,
|
||||
digest: Digest('SHA-256'),
|
||||
random: random,
|
||||
);
|
||||
final client = SRP6Client(
|
||||
group: kDefaultSrpGroup,
|
||||
digest: Digest('SHA-256'),
|
||||
random: random,
|
||||
);
|
||||
|
||||
final A = client.generateClientCredentials(salt, identity, password);
|
||||
final createSessionResponse = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/create-session",
|
||||
data: {
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||
},
|
||||
);
|
||||
final String sessionID = createSessionResponse.data["sessionID"];
|
||||
final String srpB = createSessionResponse.data["srpB"];
|
||||
final A = client.generateClientCredentials(salt, identity, password);
|
||||
final createSessionResponse = await _dio.post(
|
||||
"${_config.getHttpEndpoint()}/users/srp/create-session",
|
||||
data: {
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
|
||||
},
|
||||
);
|
||||
final String sessionID = createSessionResponse.data["sessionID"];
|
||||
final String srpB = createSessionResponse.data["srpB"];
|
||||
|
||||
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
|
||||
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/srp/verify-session",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
Widget page;
|
||||
final String twoFASessionID = response.data["twoFactorSessionID"];
|
||||
Configuration.instance.setVolatilePassword(userPassword);
|
||||
if (twoFASessionID.isNotEmpty) {
|
||||
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||
} else {
|
||||
await _saveConfiguration(response);
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
userPassword,
|
||||
Configuration.instance.getKeyAttributes()!,
|
||||
keyEncryptionKey: keyEncryptionKey,
|
||||
);
|
||||
page = const HomePage();
|
||||
} else {
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
}
|
||||
await dialog.hide();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
|
||||
// ignore: need to calculate secret to get M1, unused_local_variable
|
||||
final clientS = client.calculateSecret(serverB);
|
||||
final clientM = client.calculateClientEvidenceMessage();
|
||||
final response = await _dio.post(
|
||||
"${_config.getHttpEndpoint()}/users/srp/verify-session",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"srpUserID": srpAttributes.srpUserID,
|
||||
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
|
||||
},
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
Widget page;
|
||||
final String twoFASessionID = response.data["twoFactorSessionID"];
|
||||
Configuration.instance.setVolatilePassword(userPassword);
|
||||
if (twoFASessionID.isNotEmpty) {
|
||||
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||
} else {
|
||||
// should never reach here
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
} on DioError catch (e, s) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 401) {
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: context.l10n.incorrectPasswordTitle,
|
||||
body: context.l10n.pleaseTryAgain,
|
||||
firstButtonLabel: context.l10n.contactSupport,
|
||||
secondButtonLabel: context.l10n.ok,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
await sendLogs(
|
||||
context,
|
||||
context.l10n.contactSupport,
|
||||
"support@ente.io",
|
||||
postShare: () {},
|
||||
await _saveConfiguration(response);
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
userPassword,
|
||||
Configuration.instance.getKeyAttributes()!,
|
||||
keyEncryptionKey: keyEncryptionKey,
|
||||
);
|
||||
page = const HomePage();
|
||||
} else {
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
} else {
|
||||
_logger.fine('failed to verify password', e, s);
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
_logger.fine('failed to verify password', e, s);
|
||||
await dialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return page;
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
// should never reach here
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateKeyAttributes(KeyAttributes keyAttributes, Uint8List
|
||||
loginKey,)
|
||||
async {
|
||||
Future<void> updateKeyAttributes(
|
||||
KeyAttributes keyAttributes,
|
||||
Uint8List loginKey, {
|
||||
required bool logoutOtherDevices,
|
||||
}) async {
|
||||
try {
|
||||
final setKeyRequest = SetKeysRequest(
|
||||
kekSalt: keyAttributes.kekSalt,
|
||||
@@ -647,11 +626,11 @@ class UserService {
|
||||
memLimit: keyAttributes.memLimit,
|
||||
opsLimit: keyAttributes.opsLimit,
|
||||
);
|
||||
await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest);
|
||||
// await _enteDio.put(
|
||||
// "/users/keys",
|
||||
// data: setKeyRequest.toMap(),
|
||||
// );
|
||||
await registerOrUpdateSrp(
|
||||
loginKey,
|
||||
setKeysRequest: setKeyRequest,
|
||||
logOutOtherDevices: logoutOtherDevices,
|
||||
);
|
||||
await _config.setKeyAttributes(keyAttributes);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
@@ -679,15 +658,15 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> verifyTwoFactor(
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
String code,
|
||||
) async {
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
String code,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/verify",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/verify",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"code": code,
|
||||
@@ -703,10 +682,10 @@ class UserService {
|
||||
return const PasswordReentryPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
@@ -717,7 +696,7 @@ class UserService {
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
showErrorDialog(
|
||||
@@ -742,7 +721,7 @@ class UserService {
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/recover",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/recover",
|
||||
queryParameters: {
|
||||
"sessionID": sessionID,
|
||||
},
|
||||
@@ -758,10 +737,10 @@ class UserService {
|
||||
);
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, context.l10n.sessionExpired);
|
||||
@@ -771,7 +750,7 @@ class UserService {
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
showErrorDialog(
|
||||
@@ -793,12 +772,12 @@ class UserService {
|
||||
}
|
||||
|
||||
Future<void> removeTwoFactor(
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
String recoveryKey,
|
||||
String encryptedSecret,
|
||||
String secretDecryptionNonce,
|
||||
) async {
|
||||
BuildContext context,
|
||||
String sessionID,
|
||||
String recoveryKey,
|
||||
String encryptedSecret,
|
||||
String secretDecryptionNonce,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
String secret;
|
||||
@@ -829,7 +808,7 @@ class UserService {
|
||||
}
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/users/two-factor/remove",
|
||||
"${_config.getHttpEndpoint()}/users/two-factor/remove",
|
||||
data: {
|
||||
"sessionID": sessionID,
|
||||
"secret": secret,
|
||||
@@ -847,10 +826,10 @@ class UserService {
|
||||
return const PasswordReentryPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
}
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, "Session expired");
|
||||
@@ -860,7 +839,7 @@ class UserService {
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
(route) => route.isFirst,
|
||||
);
|
||||
} else {
|
||||
showErrorDialog(
|
||||
@@ -881,13 +860,6 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Future<void> _saveConfiguration(Response response) async {
|
||||
await Configuration.instance.setUserID(response.data["id"]);
|
||||
if (response.data["encryptedToken"] != null) {
|
||||
@@ -904,6 +876,7 @@ class UserService {
|
||||
bool? canDisableEmailMFA() {
|
||||
return _preferences.getBool(kCanDisableEmailMFA);
|
||||
}
|
||||
|
||||
bool hasEmailMFAEnabled() {
|
||||
return _preferences.getBool(kIsEmailMFAEnabled) ?? true;
|
||||
}
|
||||
@@ -918,9 +891,16 @@ class UserService {
|
||||
);
|
||||
_preferences.setBool(kIsEmailMFAEnabled, isEnabled);
|
||||
} catch (e) {
|
||||
_logger.severe("Failed to update email mfa",e);
|
||||
_logger.severe("Failed to update email mfa", e);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setRefSource(String refSource) async {
|
||||
await _preferences.setString(kReferralSource, refSource);
|
||||
}
|
||||
|
||||
String _getRefSource() {
|
||||
return _preferences.getString(kReferralSource) ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class AuthenticatorDB {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
@@ -25,6 +27,16 @@ class AuthenticatorDB {
|
||||
}
|
||||
|
||||
Future<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 =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
@@ -166,7 +178,7 @@ class AuthenticatorDB {
|
||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
final result = await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@ import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class OfflineAuthenticatorDB {
|
||||
static const _databaseName = "ente.offline_authenticator.db";
|
||||
@@ -15,7 +16,8 @@ class OfflineAuthenticatorDB {
|
||||
static const entityTable = 'entities';
|
||||
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance =
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
|
||||
static Future<Database>? _dbFuture;
|
||||
|
||||
@@ -25,8 +27,18 @@ class OfflineAuthenticatorDB {
|
||||
}
|
||||
|
||||
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 =
|
||||
await getApplicationDocumentsDirectory();
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
debugPrint(path);
|
||||
return await openDatabase(
|
||||
@@ -70,10 +82,10 @@ class OfflineAuthenticatorDB {
|
||||
}
|
||||
|
||||
Future<int> updateEntry(
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
final db = await instance.database;
|
||||
final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch;
|
||||
int affectedRows = await db.update(
|
||||
@@ -151,7 +163,7 @@ class OfflineAuthenticatorDB {
|
||||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
final result = await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
||||
@@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
||||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color _warning800 = Color(0xFFF53434);
|
||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
// ignore: unused_element
|
||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||
|
||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
||||
const ChangeEmailDialog({super.key});
|
||||
|
||||
@override
|
||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||
|
||||
@@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/common/dialogs.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
|
||||
class DeleteAccountPage extends StatelessWidget {
|
||||
const DeleteAccountPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
l10n.initiateAccountDeleteTitle,
|
||||
);
|
||||
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
final choice = await showChoiceDialogOld(
|
||||
context,
|
||||
@@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
final decryptChallenge = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(response.encryptedChallenge),
|
||||
Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
|
||||
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||
CryptoUtil.base642bin(
|
||||
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||
),
|
||||
Configuration.instance.getSecretKey()!,
|
||||
);
|
||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:password_strength/password_strength.dart';
|
||||
@@ -12,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class EmailEntryPage extends StatefulWidget {
|
||||
const EmailEntryPage({Key? key}) : super(key: key);
|
||||
const EmailEntryPage({super.key});
|
||||
|
||||
@override
|
||||
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||
@@ -30,6 +32,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
String? _email;
|
||||
String? _password;
|
||||
String _cnfPassword = '';
|
||||
String _referralSource = '';
|
||||
double _passwordStrength = 0.0;
|
||||
bool _emailIsValid = false;
|
||||
bool _hasAgreedToTOS = true;
|
||||
@@ -102,8 +105,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
isFormValid: _isFormValid(),
|
||||
buttonText: context.l10n.createAccount,
|
||||
onPressedFunction: () {
|
||||
_config.setVolatilePassword(_passwordController1.text);
|
||||
UserService.instance.setEmail(_email!);
|
||||
_config.setVolatilePassword(_passwordController1.text);
|
||||
UserService.instance.setRefSource(_referralSource);
|
||||
UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: true);
|
||||
FocusScope.of(context).unfocus();
|
||||
@@ -325,6 +329,51 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||
child: Text(
|
||||
context.l10n.hearUsWhereTitle,
|
||||
style: getEnteTextTheme(context).smallFaint,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
decoration: InputDecoration(
|
||||
fillColor: null,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
suffixIcon: InkWell(
|
||||
onTap: () {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.hearUsExplanation,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: getEnteColorScheme(context).strokeMuted,
|
||||
),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_referralSource = value.trim();
|
||||
},
|
||||
autocorrect: false,
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.next,
|
||||
),
|
||||
),
|
||||
const Divider(thickness: 1),
|
||||
const SizedBox(height: 12),
|
||||
_getAgreement(),
|
||||
@@ -378,15 +427,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -394,15 +438,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -445,15 +484,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
tags: {
|
||||
'underline': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
||||
@@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
@@ -25,6 +25,36 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Color? _emailInputFieldColor;
|
||||
final Logger _logger = Logger('_LoginPageState');
|
||||
|
||||
Future<void> onPressed() async {
|
||||
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) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = _config.getEmail();
|
||||
@@ -60,34 +90,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _emailIsValid,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
UserService.instance.setEmail(_email!);
|
||||
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) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -114,6 +117,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onFieldSubmitted:
|
||||
_emailIsValid ? (value) => onPressed() : null,
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailInputFieldColor,
|
||||
filled: true,
|
||||
@@ -176,31 +181,23 @@ class _LoginPageState extends State<LoginPage> {
|
||||
.copyWith(fontSize: 12),
|
||||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
|
||||
import "package:dio/dio.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/models/api/user/srp.dart";
|
||||
import "package:ente_auth/services/user_service.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import "package:ente_auth/ui/components/buttons/button_widget.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import "package:ente_auth/utils/email_util.dart";
|
||||
import "package:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
@@ -16,14 +19,15 @@ import "package:logging/logging.dart";
|
||||
// volatile password.
|
||||
class LoginPasswordVerificationPage extends StatefulWidget {
|
||||
final SrpAttributes srpAttributes;
|
||||
const LoginPasswordVerificationPage({Key? key, required this.srpAttributes}) : super(key: key);
|
||||
const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
|
||||
|
||||
@override
|
||||
State<LoginPasswordVerificationPage> createState() => _LoginPasswordVerificationPageState();
|
||||
State<LoginPasswordVerificationPage> createState() =>
|
||||
_LoginPasswordVerificationPageState();
|
||||
}
|
||||
|
||||
class _LoginPasswordVerificationPageState extends
|
||||
State<LoginPasswordVerificationPage> {
|
||||
class _LoginPasswordVerificationPageState
|
||||
extends State<LoginPasswordVerificationPage> {
|
||||
final _logger = Logger((_LoginPasswordVerificationPageState).toString());
|
||||
final _passwordController = TextEditingController();
|
||||
final FocusNode _passwordFocusNode = FocusNode();
|
||||
@@ -31,6 +35,11 @@ State<LoginPasswordVerificationPage> {
|
||||
bool _passwordInFocus = false;
|
||||
bool _passwordVisible = false;
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(context, _passwordController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -72,18 +81,113 @@ State<LoginPasswordVerificationPage> {
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await UserService.instance.verifyEmailViaPassword(context, widget
|
||||
.srpAttributes,
|
||||
_passwordController.text,);
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> verifyPassword(BuildContext context, String password) async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.l10n.pleaseWait,
|
||||
isDismissible: true,
|
||||
);
|
||||
await dialog.show();
|
||||
try {
|
||||
await UserService.instance.verifyEmailViaPassword(
|
||||
context,
|
||||
widget.srpAttributes,
|
||||
password,
|
||||
dialog,
|
||||
);
|
||||
} on DioException catch (e, s) {
|
||||
await dialog.hide();
|
||||
if (e.response != null && e.response!.statusCode == 401) {
|
||||
_logger.severe('server reject, failed verify SRP login', e, s);
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.incorrectPasswordTitle,
|
||||
context.l10n.pleaseTryAgain,
|
||||
);
|
||||
} else {
|
||||
_logger.severe('API failure during SRP login', e, s);
|
||||
if (e.type == DioExceptionType.unknown) {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.noInternetConnection,
|
||||
context.l10n.pleaseCheckYourInternetConnectionAndTryAgain,
|
||||
);
|
||||
} else {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e, s) {
|
||||
_logger.info('error during loginViaPassword', e);
|
||||
await dialog.hide();
|
||||
if (e is LoginKeyDerivationError) {
|
||||
_logger.severe('loginKey derivation error', e, s);
|
||||
// LoginKey err, perform regular login via ott verification
|
||||
await UserService.instance.sendOtt(
|
||||
context,
|
||||
email!,
|
||||
isCreateAccountScreen: true,
|
||||
);
|
||||
return;
|
||||
} else if (e is KeyDerivationError) {
|
||||
// device is not powerful enough to perform derive key
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: context.l10n.recreatePasswordTitle,
|
||||
body: context.l10n.recreatePasswordBody,
|
||||
firstButtonLabel: context.l10n.useRecoveryKey,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
await UserService.instance.sendOtt(
|
||||
context,
|
||||
email!,
|
||||
isResetPasswordScreen: true,
|
||||
);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
_logger.severe('unexpected error while verifying password', e, s);
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showContactSupportDialog(
|
||||
BuildContext context,
|
||||
String title,
|
||||
String message,
|
||||
) async {
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: title,
|
||||
body: message,
|
||||
firstButtonLabel: context.l10n.contactSupport,
|
||||
secondButtonLabel: context.l10n.ok,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
await sendLogs(
|
||||
context,
|
||||
context.l10n.contactSupport,
|
||||
"auth@ente.io",
|
||||
postShare: () {},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -92,17 +196,22 @@ State<LoginPasswordVerificationPage> {
|
||||
child: ListView(
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 30, left: 20, right: 20),
|
||||
padding: const EdgeInsets.only(top: 30, left: 20, right: 20),
|
||||
child: Text(
|
||||
context.l10n.enterPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30, left: 22, right:
|
||||
20,),
|
||||
child: Text(email ?? '', style: getEnteTextTheme(context).smallMuted,),
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 30,
|
||||
left: 22,
|
||||
right: 20,
|
||||
),
|
||||
child: Text(
|
||||
email ?? '',
|
||||
style: getEnteTextTheme(context).smallMuted,
|
||||
),
|
||||
),
|
||||
Visibility(
|
||||
// hidden textForm for suggesting auto-fill service for saving
|
||||
@@ -121,6 +230,9 @@ State<LoginPasswordVerificationPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
onFieldSubmitted: _passwordController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
@@ -133,19 +245,19 @@ State<LoginPasswordVerificationPage> {
|
||||
),
|
||||
suffixIcon: _passwordInFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_passwordVisible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_passwordVisible = !_passwordVisible;
|
||||
});
|
||||
},
|
||||
)
|
||||
icon: Icon(
|
||||
_passwordVisible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_passwordVisible = !_passwordVisible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -176,9 +288,11 @@ State<LoginPasswordVerificationPage> {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () async {
|
||||
await UserService.instance
|
||||
.sendOtt(context, email!,
|
||||
isResetPasswordScreen: true,);
|
||||
await UserService.instance.sendOtt(
|
||||
context,
|
||||
email!,
|
||||
isResetPasswordScreen: true,
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Text(
|
||||
@@ -187,9 +301,9 @@ State<LoginPasswordVerificationPage> {
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -213,9 +327,9 @@ State<LoginPasswordVerificationPage> {
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -229,4 +343,4 @@ State<LoginPasswordVerificationPage> {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
|
||||
this.isChangeEmail = false,
|
||||
this.isCreateAccountScreen = false,
|
||||
this.isResetPasswordScreen = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
||||
@@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
|
||||
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
body: _getBody(),
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: !(_verificationCodeController.text.isEmpty),
|
||||
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||
buttonText: l10n.verify,
|
||||
onPressedFunction: () {
|
||||
if (widget.isChangeEmail) {
|
||||
UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
UserService.instance
|
||||
.verifyEmail(context, _verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
onFieldSubmitted: _verificationCodeController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
hintText: l10n.tapToEnterCode,
|
||||
|
||||
@@ -4,10 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/recovery_key_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -24,8 +25,7 @@ enum PasswordEntryMode {
|
||||
class PasswordEntryPage extends StatefulWidget {
|
||||
final PasswordEntryMode mode;
|
||||
|
||||
const PasswordEntryPage({required this.mode, Key? key})
|
||||
: super(key: key);
|
||||
const PasswordEntryPage({required this.mode, super.key});
|
||||
|
||||
@override
|
||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
@@ -60,7 +60,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
if (_volatilePassword != null) {
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => _showRecoveryCodeDialog(_volatilePassword!),
|
||||
() => _showRecoveryCodeDialog(
|
||||
_volatilePassword!,
|
||||
usingVolatilePassword: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
_password1FocusNode.addListener(() {
|
||||
@@ -180,10 +183,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
.copyWith(fontSize: 14),
|
||||
tags: {
|
||||
'underline': StyledTextTag(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -313,8 +317,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
onChanged: (cnfPassword) {
|
||||
setState(() {
|
||||
_passwordInInputConfirmationBox = cnfPassword;
|
||||
if (_passwordInInputBox != null ||
|
||||
_passwordInInputBox != '') {
|
||||
if (_passwordInInputBox != '') {
|
||||
_passwordsMatch = _passwordInInputBox ==
|
||||
_passwordInInputConfirmationBox;
|
||||
}
|
||||
@@ -340,15 +343,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
@@ -356,10 +354,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.l10n.howItWorks,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -374,13 +373,18 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
|
||||
void _updatePassword() async {
|
||||
final logOutFromOthers = await logOutFromOtherDevices(context);
|
||||
final dialog =
|
||||
createProgressDialog(context, context.l10n.generatingEncryptionKeys);
|
||||
await dialog.show();
|
||||
try {
|
||||
final result = await Configuration.instance
|
||||
.getAttributesForNewPassword(_passwordController1.text);
|
||||
await UserService.instance.updateKeyAttributes(result.item1, result.item2);
|
||||
await UserService.instance.updateKeyAttributes(
|
||||
result.item1,
|
||||
result.item2,
|
||||
logoutOtherDevices: logOutFromOthers,
|
||||
);
|
||||
await dialog.hide();
|
||||
showShortToast(context, context.l10n.passwordChangedSuccessfully);
|
||||
Navigator.of(context).pop();
|
||||
@@ -394,14 +398,41 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showRecoveryCodeDialog(String password) async {
|
||||
Future<bool> logOutFromOtherDevices(BuildContext context) async {
|
||||
bool logOutFromOther = true;
|
||||
await showChoiceDialog(
|
||||
context,
|
||||
title: context.l10n.signOutFromOtherDevices,
|
||||
body: context.l10n.signOutOtherBody,
|
||||
isDismissible: false,
|
||||
firstButtonLabel: context.l10n.signOutOtherDevices,
|
||||
firstButtonType: ButtonType.critical,
|
||||
firstButtonOnTap: () async {
|
||||
logOutFromOther = true;
|
||||
},
|
||||
secondButtonLabel: context.l10n.doNotSignOut,
|
||||
secondButtonOnTap: () async {
|
||||
logOutFromOther = false;
|
||||
},
|
||||
);
|
||||
return logOutFromOther;
|
||||
}
|
||||
|
||||
Future<void> _showRecoveryCodeDialog(
|
||||
String password, {
|
||||
bool usingVolatilePassword = false,
|
||||
}) async {
|
||||
final l10n = context.l10n;
|
||||
final dialog =
|
||||
createProgressDialog(context, l10n.generatingEncryptionKeysTitle);
|
||||
await dialog.show();
|
||||
try {
|
||||
final KeyGenResult result = await Configuration.instance.generateKey(password);
|
||||
Configuration.instance.setVolatilePassword(null);
|
||||
if (usingVolatilePassword) {
|
||||
_logger.info('Using volatile password');
|
||||
}
|
||||
final KeyGenResult result =
|
||||
await Configuration.instance.generateKey(password);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
await dialog.hide();
|
||||
onDone() async {
|
||||
final dialog = createProgressDialog(context, l10n.pleaseWait);
|
||||
@@ -409,7 +440,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
try {
|
||||
await UserService.instance.setAttributes(result);
|
||||
await dialog.hide();
|
||||
Configuration.instance.setVolatilePassword(null);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PasswordReentryPage extends StatefulWidget {
|
||||
const PasswordReentryPage({Key? key}) : super(key: key);
|
||||
const PasswordReentryPage({super.key});
|
||||
|
||||
@override
|
||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||
@@ -40,7 +40,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
_passwordController.text = _volatilePassword!;
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => verifyPassword(_volatilePassword!),
|
||||
() => verifyPassword(_volatilePassword!, usingVolatilePassword: true),
|
||||
);
|
||||
}
|
||||
_passwordFocusNode.addListener(() {
|
||||
@@ -90,11 +90,16 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> verifyPassword(String password) async {
|
||||
Future<void> verifyPassword(
|
||||
String password, {
|
||||
bool usingVolatilePassword = false,
|
||||
}) async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog =
|
||||
createProgressDialog(context, context.l10n.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
if (usingVolatilePassword) {
|
||||
_logger.info("Using volatile password");
|
||||
}
|
||||
try {
|
||||
final kek = await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
password,
|
||||
@@ -140,8 +145,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
}
|
||||
return;
|
||||
}
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
await dialog.hide();
|
||||
Configuration.instance.setVolatilePassword(null);
|
||||
unawaited(
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
@@ -149,7 +154,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
(route) => false,
|
||||
(route) => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -183,7 +188,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
children: [
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
context.l10n.welcomeBack,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
@@ -218,19 +223,19 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
),
|
||||
suffixIcon: _passwordInFocus
|
||||
? IconButton(
|
||||
icon: Icon(
|
||||
_passwordVisible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_passwordVisible = !_passwordVisible;
|
||||
});
|
||||
},
|
||||
)
|
||||
icon: Icon(
|
||||
_passwordVisible
|
||||
? Icons.visibility
|
||||
: Icons.visibility_off,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
size: 20,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_passwordVisible = !_passwordVisible;
|
||||
});
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -276,9 +281,9 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -302,9 +307,9 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
const RecoveryKeyPage(
|
||||
this.recoveryKey,
|
||||
this.doneText, {
|
||||
Key? key,
|
||||
super.key,
|
||||
this.showAppBar,
|
||||
this.onDone,
|
||||
this.isDismissible,
|
||||
@@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
this.text,
|
||||
this.subText,
|
||||
this.showProgressBar = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||
@@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
bool _hasTriedToSave = false;
|
||||
final _recoveryKeyFile = io.File(
|
||||
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
|
||||
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
? 32
|
||||
: 120;
|
||||
|
||||
Future<void> copy() async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: recoveryKey,
|
||||
),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: widget.showProgressBar
|
||||
? AppBar(
|
||||
@@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||
DottedBorder(
|
||||
color: const Color.fromARGB(255, 105, 17, 127),
|
||||
//color of dotted/dash line
|
||||
strokeWidth: 1,
|
||||
//thickness of dash/dots
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
//dash patterns, 10 is dash width, 6 is space width
|
||||
child: SizedBox(
|
||||
//inner container
|
||||
// height: 120, //height of inner container
|
||||
width: double
|
||||
.infinity, //width to 100% match to parent container.
|
||||
// ignore: prefer_const_literals_to_create_immutables
|
||||
child: Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: recoveryKey),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(
|
||||
49,
|
||||
155,
|
||||
86,
|
||||
.2,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x8E9610D6),
|
||||
Color(0x8E9F4FC6),
|
||||
],
|
||||
stops: [0.0, 0.9753],
|
||||
),
|
||||
),
|
||||
child: DottedBorder(
|
||||
padding: EdgeInsets.zero,
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1,
|
||||
color: const Color(0xFF6B6B6B),
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final content = Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge,
|
||||
),
|
||||
);
|
||||
|
||||
if (PlatformUtil.isMobile()) {
|
||||
return GestureDetector(
|
||||
onTap: () async => await copy(),
|
||||
child: content,
|
||||
);
|
||||
} else {
|
||||
return SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls:
|
||||
PlatformUtil.selectionControls,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.recoveryKeyBoxColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: PlatformCopy(
|
||||
onPressed: copy,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
),
|
||||
),
|
||||
],
|
||||
), // columnEnds
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
final List<Widget> childrens = [];
|
||||
if (!_hasTriedToSave) {
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
),
|
||||
),
|
||||
);
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
@@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
childrens.add(
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
shareDialog(
|
||||
context,
|
||||
context.l10n.recoveryKey,
|
||||
saveAction: () async {
|
||||
await _saveRecoveryKey(recoveryKey);
|
||||
},
|
||||
sendAction: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
text: context.l10n.saveKey,
|
||||
),
|
||||
);
|
||||
|
||||
if (_hasTriedToSave) {
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
return childrens;
|
||||
}
|
||||
|
||||
Future _saveRecoveryKey(String recoveryKey) async {
|
||||
final bytes = utf8.encode(recoveryKey);
|
||||
final time = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
await PlatformUtil.shareFile(
|
||||
"ente_recovery_key_$time",
|
||||
"txt",
|
||||
bytes,
|
||||
MimeType.text,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.recoveryKeySaved,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future _shareRecoveryKey(String recoveryKey) async {
|
||||
if (_recoveryKeyFile.existsSync()) {
|
||||
await _recoveryKeyFile.delete();
|
||||
}
|
||||
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
||||
// ignore: deprecated_member_use
|
||||
await Share.shareFiles([_recoveryKeyFile.path]);
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
@@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
widget.onDone!();
|
||||
}
|
||||
}
|
||||
|
||||
class PlatformCopy extends StatelessWidget {
|
||||
const PlatformCopy({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final void Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () => onPressed(),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
@@ -10,7 +9,7 @@ import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecoveryPage extends StatefulWidget {
|
||||
const RecoveryPage({Key? key}) : super(key: key);
|
||||
const RecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||
@@ -19,6 +18,36 @@ class RecoveryPage extends StatefulWidget {
|
||||
class _RecoveryPageState extends State<RecoveryPage> {
|
||||
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!");
|
||||
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}';
|
||||
}
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
@@ -46,35 +75,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _recoveryKey.text.isNotEmpty,
|
||||
buttonText: 'Recover',
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
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}';
|
||||
}
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
@@ -87,7 +88,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
'Forgot password',
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -139,11 +140,13 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
child: Center(
|
||||
child: Text(
|
||||
"No recovery key?",
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import "package:ente_auth/utils/crypto_util.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_sodium/flutter_sodium.dart";
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||
@@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
|
||||
final OnPasswordVerifiedFn onPasswordVerified;
|
||||
final Function? onPasswordError;
|
||||
|
||||
const RequestPasswordVerificationPage(
|
||||
{super.key, required this.onPasswordVerified, this.onPasswordError,});
|
||||
const RequestPasswordVerificationPage({
|
||||
super.key,
|
||||
required this.onPasswordVerified,
|
||||
this.onPasswordError,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RequestPasswordVerificationPage> createState() =>
|
||||
@@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
|
||||
try {
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(_passwordController.text) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(_passwordController.text),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
dialog.show();
|
||||
// pop
|
||||
|
||||
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class SessionsPage extends StatefulWidget {
|
||||
const SessionsPage({Key? key}) : super(key: key);
|
||||
const SessionsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SessionsPage> createState() => _SessionsPageState();
|
||||
|
||||
@@ -12,12 +12,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class VerifyRecoveryPage extends StatefulWidget {
|
||||
const VerifyRecoveryPage({Key? key}) : super(key: key);
|
||||
const VerifyRecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
||||
@@ -34,14 +35,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
try {
|
||||
final String inputKey = _recoveryKey.text.trim();
|
||||
final String recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
||||
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
||||
try {
|
||||
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
if (e is DioError && e.type == DioErrorType.other) {
|
||||
if (e is DioException && e.type == DioExceptionType.unknown) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
"No internet connection",
|
||||
@@ -88,10 +89,13 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
||||
context,
|
||||
"Please authenticate to view your recovery key",
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
recoveryKey =
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
|
||||
@@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
|
||||
final int period;
|
||||
|
||||
CodeTimerProgress({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.period,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
_CodeTimerProgressState createState() => _CodeTimerProgressState();
|
||||
State createState() => _CodeTimerProgressState();
|
||||
}
|
||||
|
||||
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/code_timer_progress.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -24,7 +25,7 @@ import 'package:move_to_background/move_to_background.dart';
|
||||
class CodeWidget extends StatefulWidget {
|
||||
final Code code;
|
||||
|
||||
const CodeWidget(this.code, {Key? key}) : super(key: key);
|
||||
const CodeWidget(this.code, {super.key});
|
||||
|
||||
@override
|
||||
State<CodeWidget> createState() => _CodeWidgetState();
|
||||
@@ -372,9 +373,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
Future<void> _onEditPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
@@ -390,9 +392,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
Future<void> _onShowQrPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
// ignore: unused_local_variable
|
||||
@@ -406,14 +409,15 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
}
|
||||
|
||||
void _onDeletePressed(_) async {
|
||||
bool _isAuthSuccessful =
|
||||
bool isAuthSuccessful =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.deleteCodeAuthMessage,
|
||||
);
|
||||
if (!_isAuthSuccessful) {
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
@@ -450,7 +454,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
code = code.replaceAll(RegExp(r'\d'), '•');
|
||||
}
|
||||
if (code.length == 6) {
|
||||
return code.substring(0, 3) + " " + code.substring(3, 6);
|
||||
return "${code.substring(0, 3)} ${code.substring(3, 6)}";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
|
||||
class BottomShadowWidget extends StatelessWidget {
|
||||
final double offsetDy;
|
||||
final Color? shadowColor;
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
|
||||
: super(key: key);
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||