Compare commits
1 Commits
it_auth
...
ua741-patc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f1f1a5871 |
5
.github/workflows/mobile-daily-internal.yml
vendored
5
.github/workflows/mobile-daily-internal.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.86.0"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -47,9 +47,6 @@ jobs:
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Generate Rust bindings
|
||||
run: flutter_rust_bridge_codegen generate
|
||||
|
||||
- name: Increment version code for build
|
||||
run: |
|
||||
CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
|
||||
|
||||
14
.github/workflows/mobile-lint.yml
vendored
14
.github/workflows/mobile-lint.yml
vendored
@@ -9,7 +9,6 @@ on:
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.32.8"
|
||||
RUST_VERSION: "1.86.0"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -32,18 +31,7 @@ jobs:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
|
||||
- run: flutter pub get
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Generate Rust bindings
|
||||
run: flutter_rust_bridge_codegen generate
|
||||
|
||||
- run: flutter analyze --no-fatal-infos
|
||||
|
||||
@@ -142,22 +142,6 @@ var _updateFreeUserStorage = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var _sendMail = &cobra.Command{
|
||||
Use: "send-mail <to-email> <from-email> <from-name>",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Short: "Sends a test mail via the admin api",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.SendTestMail(context.Background(), *flags, args[0], args[1], args[2])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
@@ -175,6 +159,5 @@ func init() {
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_sendMail.Flags().StringP("admin-user", "a", "", "The email of the admin user. ")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser, _sendMail)
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,10 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/ente-io/cli/pkg"
|
||||
"github.com/spf13/cobra/doc"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
@@ -21,6 +20,11 @@ var ctrl *pkg.ClICtrl
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ente",
|
||||
Short: "CLI tool for exporting your photos from ente.io",
|
||||
// Uncomment the following line if your bare application
|
||||
// has an action associated with it:
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Sprintf("Hello World")
|
||||
},
|
||||
}
|
||||
|
||||
func GenerateDocs() error {
|
||||
|
||||
@@ -139,28 +139,5 @@ func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.User
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) SendTestMail(ctx context.Context, toEmail, fromEmail, fromName string) error {
|
||||
body := map[string]interface{}{
|
||||
"to": []string{toEmail},
|
||||
"fromName": fromName,
|
||||
"fromEmail": fromEmail,
|
||||
"subject": "Test mail from Ente",
|
||||
"body": "This is a test mail from Ente",
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetBody(body).
|
||||
Post("/admin/mail")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,23 +156,6 @@ func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) SendTestMail(ctx context.Context, params model.AdminActionForUser, to, from, fromName string) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.SendTestMail(accountCtx, to, from, fromName)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*api.ApiError); ok && apiErr.StatusCode == 400 && strings.Contains(apiErr.Message, "Token is too old") {
|
||||
fmt.Printf("Error: old admin token, please re-authenticate using `ente account add` \n")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully sent test email to %s\n", to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
## v1.7.15 (Unreleased)
|
||||
|
||||
- Custom domains.
|
||||
- Support Czech translations.
|
||||
- .
|
||||
|
||||
## v1.7.14
|
||||
|
||||
@@ -8,12 +8,6 @@ description: Guide to configuring Ente CLI for Self Hosted Instance
|
||||
If you are self-hosting, you can configure Ente CLI to export data & perform
|
||||
basic administrative actions.
|
||||
|
||||
::: tip Installing Ente CLI
|
||||
|
||||
For instructions on installing the Ente CLI, see the [README available on Github](https://github.com/ente-io/ente/tree/main/cli/README.md).
|
||||
|
||||
:::
|
||||
|
||||
## Step 1: Configure endpoint
|
||||
|
||||
To do this, first configure the CLI to use your server's endpoint.
|
||||
|
||||
@@ -63,20 +63,11 @@ It has no relation to Backblaze, Wasabi or Scaleway.
|
||||
Each bucket's endpoint, region, key and secret should be configured accordingly
|
||||
if using an external bucket.
|
||||
|
||||
If a bucket has SSL support enabled, set `s3.are_local_buckets` to `false`. Enable path-style URL by setting `s3.use_path_style_urls` to `true`.
|
||||
|
||||
::: note
|
||||
|
||||
You can configure this for individual buckets over defining top-level configuration if you are using the latest server image (August 2025)
|
||||
|
||||
:::
|
||||
|
||||
A sample configuration for `b2-eu-cen` is provided, which can be used for other 2 buckets as well:
|
||||
A sample configuration for `b2-eu-cen` is provided, which can be used for other
|
||||
2 buckets as well:
|
||||
|
||||
```yaml
|
||||
b2-eu-cen:
|
||||
are_local_buckets: true
|
||||
use_path_style_urls: true
|
||||
key: <key>
|
||||
secret: <secret>
|
||||
endpoint: localhost:3200
|
||||
|
||||
@@ -22,7 +22,8 @@ can achieve this the following steps:
|
||||
# Change the DB name and DB user name if you use different
|
||||
# values.
|
||||
# If using Docker
|
||||
docker exec -it <postgres-ente-container-name> sh
|
||||
|
||||
docker exec -it <postgres-ente-container-name>
|
||||
psql -U pguser -d ente_db
|
||||
|
||||
# Or when using psql directly
|
||||
|
||||
@@ -96,8 +96,8 @@ provide correct credentials for proper connectivity within Museum.
|
||||
The `s3` section within `museum.yaml` is by default configured to use local
|
||||
MinIO buckets when using `quickstart.sh` or Docker Compose.
|
||||
|
||||
If you wish to use an external S3 provider with SSL, you can edit the configuration with
|
||||
your provider's credentials, and set `s3.are_local_buckets` to `false`. Additionally, you can configure this for specific buckets in the corresponding bucket sections in the Compose file.
|
||||
If you wish to use an external S3 provider, you can edit the configuration with
|
||||
your provider's credentials, and set `s3.are_local_buckets` to `false`.
|
||||
|
||||
If you are using default MinIO, it is accessible at port `3200`. Web Console can
|
||||
be accessed by enabling port `3201` in the Compose file.
|
||||
@@ -111,11 +111,11 @@ and [troubleshooting](/self-hosting/troubleshooting/uploads) sections.
|
||||
|
||||
| Variable | Description | Default |
|
||||
| -------------------------------------- | -------------------------------------------- | ------- |
|
||||
| `s3.b2-eu-cen` | Primary hot storage bucket configuration | |
|
||||
| `s3.b2-eu-cen` | Primary hot storage S3 config | |
|
||||
| `s3.wasabi-eu-central-2-v3.compliance` | Whether to disable compliance lock on delete | `true` |
|
||||
| `s3.scw-eu-fr-v3` | Cold storage bucket configuration | |
|
||||
| `s3.wasabi-eu-central-2-v3` | Secondary hot storage configuration | |
|
||||
| `s3.are_local_buckets` | | `true` |
|
||||
| `s3.scw-eu-fr-v3` | Optional secondary S3 config | |
|
||||
| `s3.wasabi-eu-central-2-derived` | Derived data storage | |
|
||||
| `s3.are_local_buckets` | Use local MinIO-compatible storage | `false` |
|
||||
| `s3.use_path_style_urls` | Enable path-style URLs for MinIO | `false` |
|
||||
|
||||
### Encryption Keys
|
||||
@@ -171,8 +171,6 @@ smtp:
|
||||
email:
|
||||
# Optional name for sender
|
||||
sender-name:
|
||||
# Optional encryption
|
||||
encryption:
|
||||
```
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -183,7 +181,6 @@ smtp:
|
||||
| `smtp.password` | SMTP auth password | |
|
||||
| `smtp.email` | Sender email address | |
|
||||
| `smtp.sender-name` | Custom name for email sender | |
|
||||
| `smtp.encryption` | Encryption method (tls, ssl) | |
|
||||
| `transmail.key` | Zeptomail API key | |
|
||||
|
||||
### WebAuthn Passkey Support
|
||||
|
||||
@@ -46,7 +46,7 @@ If running Museum without Docker, the code should be visible in the terminal
|
||||
# Change the DB name and DB user name if you use different
|
||||
# values.
|
||||
|
||||
# If using Docker docker exec -it <postgres-ente-container-name> sh
|
||||
# If using Docker docker exec -it <postgres-ente-container-name>
|
||||
psql -U pguser -d ente_db
|
||||
|
||||
# Or when using psql directly
|
||||
|
||||
45
mobile/.gitignore
vendored
45
mobile/.gitignore
vendored
@@ -1,45 +0,0 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
melos_*.iml
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
@@ -1,72 +0,0 @@
|
||||
# For more linters, we can check https://dart-lang.github.io/linter/lints/index.html
|
||||
# or https://pub.dev/packages/lint (Effective dart)
|
||||
# use "flutter analyze ." or "dart analyze ." for running lint checks
|
||||
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
linter:
|
||||
rules:
|
||||
# Ref https://github.com/flutter/packages/blob/master/packages/flutter_lints/lib/flutter.yaml
|
||||
# Ref https://dart-lang.github.io/linter/lints/
|
||||
- avoid_print
|
||||
- avoid_unnecessary_containers
|
||||
- avoid_web_libraries_in_flutter
|
||||
- no_logic_in_create_state
|
||||
- prefer_const_constructors
|
||||
- prefer_const_constructors_in_immutables
|
||||
- prefer_const_declarations
|
||||
- prefer_const_literals_to_create_immutables
|
||||
- prefer_final_locals
|
||||
- require_trailing_commas
|
||||
- sized_box_for_whitespace
|
||||
- use_full_hex_values_for_flutter_colors
|
||||
- use_key_in_widget_constructors
|
||||
- cancel_subscriptions
|
||||
|
||||
|
||||
- avoid_empty_else
|
||||
- exhaustive_cases
|
||||
|
||||
# just style suggestions
|
||||
- sort_pub_dependencies
|
||||
- use_rethrow_when_possible
|
||||
- prefer_double_quotes
|
||||
- directives_ordering
|
||||
- always_use_package_imports
|
||||
- sort_child_properties_last
|
||||
- unawaited_futures
|
||||
|
||||
analyzer:
|
||||
errors:
|
||||
avoid_empty_else: error
|
||||
exhaustive_cases: error
|
||||
curly_braces_in_flow_control_structures: error
|
||||
directives_ordering: error
|
||||
require_trailing_commas: error
|
||||
always_use_package_imports: warning
|
||||
prefer_final_fields: error
|
||||
unused_import: error
|
||||
camel_case_types: error
|
||||
prefer_is_empty: warning
|
||||
use_rethrow_when_possible: info
|
||||
unused_field: warning
|
||||
use_key_in_widget_constructors: warning
|
||||
sort_child_properties_last: warning
|
||||
sort_pub_dependencies: warning
|
||||
library_private_types_in_public_api: warning
|
||||
constant_identifier_names: ignore
|
||||
prefer_const_constructors: warning
|
||||
prefer_const_declarations: warning
|
||||
prefer_const_constructors_in_immutables: warning
|
||||
prefer_final_locals: warning
|
||||
unnecessary_const: error
|
||||
cancel_subscriptions: error
|
||||
unrelated_type_equality_checks: error
|
||||
unnecessary_cast: info
|
||||
|
||||
|
||||
unawaited_futures: warning # convert to warning after fixing existing issues
|
||||
invalid_dependency: info
|
||||
use_build_context_synchronously: ignore # experimental lint, requires many changes
|
||||
prefer_interpolation_to_compose_strings: ignore # later too many warnings
|
||||
prefer_double_quotes: ignore # too many warnings
|
||||
avoid_renaming_method_parameters: ignore # incorrect warnings for `equals` overrides
|
||||
2
mobile/apps/auth/.gitignore
vendored
2
mobile/apps/auth/.gitignore
vendored
@@ -33,8 +33,6 @@
|
||||
.pub/
|
||||
/build/
|
||||
macos/build/
|
||||
.gradle/
|
||||
settings.local.json
|
||||
|
||||
# Web related
|
||||
lib/generated_plugin_registrant.dart
|
||||
|
||||
@@ -34,9 +34,6 @@ android {
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
// Sets Java compatibility to Java 8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
@@ -59,7 +56,7 @@ android {
|
||||
applicationId "io.ente.auth"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||
minSdkVersion 22
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 35
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
@@ -118,7 +115,4 @@ flutter {
|
||||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// For AGP 7.4+
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
}
|
||||
dependencies {}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
# Please add these rules to your existing keep rules in order to suppress warnings.
|
||||
# This is generated automatically by the Android Gradle plugin.
|
||||
-dontwarn com.google.errorprone.annotations.CanIgnoreReturnValue
|
||||
-dontwarn com.google.errorprone.annotations.CheckReturnValue
|
||||
-dontwarn com.google.errorprone.annotations.Immutable
|
||||
-dontwarn com.google.errorprone.annotations.RestrictedApi
|
||||
@@ -6,19 +6,6 @@ allprojects {
|
||||
}
|
||||
|
||||
rootProject.buildDir = '../build'
|
||||
|
||||
subprojects {
|
||||
afterEvaluate { project ->
|
||||
if (project.hasProperty('android')) {
|
||||
project.android {
|
||||
if (namespace == null) {
|
||||
namespace project.group
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
||||
|
||||
@@ -19,8 +19,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version "8.6.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
id "com.android.application" version "7.3.0" apply false
|
||||
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
@@ -382,11 +382,6 @@
|
||||
{
|
||||
"title": "CoinDCX"
|
||||
},
|
||||
{
|
||||
"title": "CoinTracking",
|
||||
"slug": "cointracking",
|
||||
"altNames": ["cointracking.info", "Coin Tracking"]
|
||||
},
|
||||
{
|
||||
"title": "colorado",
|
||||
"altNames": [
|
||||
@@ -740,11 +735,6 @@
|
||||
{
|
||||
"title": "Hivelocity"
|
||||
},
|
||||
{
|
||||
"title": "HRDocumentBox",
|
||||
"slug": "hrdocumentbox",
|
||||
"altNames": ["HRDocumentBox", "HR Document Box"]
|
||||
},
|
||||
{
|
||||
"title": "HSA Bank",
|
||||
"slug": "hsa_bank",
|
||||
@@ -1050,13 +1040,6 @@
|
||||
"MistralAI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mobile01",
|
||||
"slug": "mobile01",
|
||||
"altNames": [
|
||||
"M01"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mozilla"
|
||||
},
|
||||
@@ -1790,11 +1773,6 @@
|
||||
"uollet.com.br"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "VHV",
|
||||
"slug": "vhv",
|
||||
"altNames": ["VHV", "VHV Versicherung"]
|
||||
},
|
||||
{
|
||||
"title": "Vikunja"
|
||||
},
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1220.8 227.9" style="enable-background:new 0 0 1220.8 227.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#0D253E;}
|
||||
.st1{fill:#008AFB;}
|
||||
</style>
|
||||
<title>CoinTracking light</title>
|
||||
<g id="Layer_2_1_">
|
||||
<g id="Layer_1-2">
|
||||
<path class="st0" d="M198.1,167c-30.2,0-54.7-24.5-54.7-54.7s24.5-54.7,54.7-54.7s54.7,24.5,54.7,54.7l0,0 C252.8,142.5,228.3,167,198.1,167z M198.1,81.6c-17,0-30.7,13.7-30.7,30.7s13.7,30.7,30.7,30.7s30.7-13.7,30.7-30.7l0,0 C228.8,95.4,215,81.6,198.1,81.6z"/>
|
||||
<path class="st0" d="M292.2,59.5h-23.6v107.9h23.6V59.5z"/>
|
||||
<path class="st0" d="M339.5,167.4h-23.8V59.5h23.8v16.2c6.2-12.5,21-18.5,33-18.5c26.1,0,41.1,16.9,41.1,47.3v62.8h-23.8v-60.1 c0-17.1-8.8-26.8-22.6-26.8c-14.1,0-27.7,7.6-27.7,28.9V167.4z"/>
|
||||
<path class="st1" d="M390.6,8.2h151.7v22.9h-65.9v136.3h-25V31.1h-60.9V8.2H390.6z"/>
|
||||
<path class="st1" d="M540.3,167.4h-23.8V59.5h23.8V79c7.4-13.5,15.4-19.5,29.6-21.8c7.5-1.2,15.7,1.8,19.2,4.2l-3.9,22 c-4.9-2.5-10.4-3.8-15.9-3.7c-20.3,0-28.9,20.3-28.9,49L540.3,167.4L540.3,167.4z"/>
|
||||
<path class="st1" d="M680.7,151.9c-7.2,11.8-22.9,17.8-36.3,17.8c-29.1,0-54.8-21.9-54.8-56.4s25.6-56.1,54.8-56.1 c12.9,0,28.9,5.3,36.3,17.5V59.5h23.6v107.9h-23.6V151.9z M647.2,146.6c17.6,0,33.3-12.2,33.3-33.5s-17.1-32.8-33.3-32.8 c-18,0-33,12.9-33,32.8S629.2,146.6,647.2,146.6L647.2,146.6z"/>
|
||||
<path class="st1" d="M778.4,57.2c17.1,0,32.6,6.7,42.7,18.7l-18.5,14.8c-5.8-6.7-14.8-10.4-24.3-10.4c-18,0-34,12.7-34,32.8 s15.9,33.7,34,33.7c9.5,0,18.5-3.9,24.3-10.6l18.7,14.6c-10.2,12-25.6,18.9-43,18.9c-31,0-57.5-22.4-57.5-56.6 S747.4,57.2,778.4,57.2z"/>
|
||||
<path class="st1" d="M860.3,117.2v50.1h-23.6V0.8h23.6V95l34.2-35.6h31.9l-46,46.9l56.4,61h-30.5L860.3,117.2z"/>
|
||||
<path class="st1" d="M967.4,59.5h-23.6v107.9h23.6V59.5z"/>
|
||||
<path class="st1" d="M1014.7,167.4h-23.8V59.5h23.8v16.2c6.2-12.5,21-18.5,33-18.5c26.1,0,41.1,16.9,41.1,47.3v62.8H1065v-60.1 c0-17.1-8.8-26.8-22.6-26.8c-14.1,0-27.7,7.6-27.7,28.9V167.4z"/>
|
||||
<path class="st1" d="M1220.2,111.5c0-12.3-4.5-24.2-12.5-33.6l13-17.1l-19.6-13l-11.9,15.7c-8.3-4.1-17.5-6.2-26.8-6.2 c-31.9,0-57.8,24.4-57.8,54.3s25.9,54.3,57.8,54.3c20.2,0,34.2,10.2,34.2,19.3s-14.1,19.3-34.2,19.3s-34.2-10.2-34.2-19.3h-23.6 c0,24,25.4,42.9,57.8,42.9s57.8-18.8,57.8-42.9c0-13.2-7.7-24.8-19.9-32.6C1212.5,142.5,1220.2,127.8,1220.2,111.5z M1128.2,111.5 c0-16.9,15.4-30.7,34.2-30.7s34.2,13.8,34.2,30.7s-15.4,30.7-34.2,30.7S1128.2,128.4,1128.2,111.5L1128.2,111.5z"/>
|
||||
<path class="st0" d="M81.4,170.5C36.4,170.5,0,134,0,89c0-21.6,8.6-42.3,23.9-57.6c31.8-31.8,83.4-31.8,115.2,0l0,0l-17.7,17.7 C99.3,27,63.6,27,41.5,49.1s-22.1,57.8,0,79.9s57.8,22.1,79.9,0l17.7,17.7C123.8,162,103.1,170.6,81.4,170.5z"/>
|
||||
<circle class="st0" cx="280.7" cy="15.5" r="15.5"/>
|
||||
<circle class="st1" cx="954.7" cy="15.5" r="15.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.0 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg width="320" height="280" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1">
|
||||
|
||||
<g class="layer">
|
||||
<title>Layer 1</title>
|
||||
<g id="Layer1000">
|
||||
<g id="Layer1002">
|
||||
<g id="Layer1003">
|
||||
<path d="m123.08,34.29c-66.43,0 -120.27,53.85 -120.27,120.27c0,66.43 53.85,120.27 120.27,120.27c66.47,0 120.32,-53.85 120.32,-120.27c0,-66.43 -53.85,-120.27 -120.32,-120.27zm0,215.67c-52.67,0 -95.36,-42.73 -95.36,-95.4c0,-52.67 42.68,-95.4 95.36,-95.4c52.72,0 95.4,42.73 95.4,95.4c0,52.67 -42.68,95.4 -95.4,95.4z" fill="#2a5e00" fill-rule="evenodd" id="path7"/>
|
||||
<g id="Layer1004">
|
||||
<g id="Layer1005">
|
||||
<path d="m138.72,146.29l59.61,-41.47l7.78,33.7l-67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path8"/>
|
||||
<path d="m110.88,146.29l-59.61,-41.47l-7.78,33.7l67.39,7.78z" fill="#2a5e00" fill-rule="evenodd" id="path9"/>
|
||||
</g>
|
||||
<path d="m43.95,192.02l74.62,49.75l87.12,-78.8l-161.75,29.05z" fill="#2a5e00" fill-rule="evenodd" id="path10"/>
|
||||
</g>
|
||||
<path d="m94.24,59.29l-30.48,-55.1l54.26,33.24l-23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path11"/>
|
||||
<path d="m202.64,78.1l30.43,-55.1l-54.22,33.24l23.79,21.86z" fill="#2a5e00" fill-rule="evenodd" id="path12"/>
|
||||
</g>
|
||||
<path d="m275.63,274.67l29.35,0l0,-240.76l-29.35,0l0,240.76z" fill="#2a5e00" fill-rule="evenodd" id="path13"/>
|
||||
<path d="m317.94,125.93c0,15.3 -12.33,27.63 -27.63,27.63c-15.26,0 -27.63,-12.33 -27.63,-27.63c0,-15.26 12.37,-27.63 27.63,-27.63c15.3,0 27.63,12.37 27.63,27.63z" fill="#2a5e00" fill-rule="evenodd" id="path14"/>
|
||||
<path d="m288.84,33.91l-41.76,0l16.76,45.99l23.58,0l1.42,-45.99z" fill="#2a5e00" fill-rule="evenodd" id="path15"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) by Marsupilami -->
|
||||
<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="1024" height="357" viewBox="-1.98252 -1.98252 201.02104 70.04904" id="svg3349">
|
||||
<defs id="defs3351"/>
|
||||
<path d="m 0,0 11.76,0 4.455,31.962 0.12,0 L 20.789,0 32.55,0 23.402,42.417 9.147,42.417 0,0" id="path3131" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 34.039,0 11.05,0 0,15.567 6.771,0 0,-15.567 11.05,0 0,42.417 -11.05,0 0,-17.465 -6.771,0 0,17.465 -11.05,0 0,-42.417" id="path3133" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 64.395,0 11.757,0 4.457,31.962 0.121,0 L 85.185,0 96.944,0 87.797,42.417 73.54,42.417 64.395,0" id="path3135" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 97.576,42.414 8.907,0 9.222,-42.41 -8.912,0 -9.217,42.41" id="path3137" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 111.026,42.424 8.908,-0.01 9.218,-42.41 -8.913,0 -9.213,42.42" id="path3139" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 124.564,42.414 8.906,0 9.217,-42.41 -8.906,0 -9.217,42.41" id="path3141" style="fill:#f0ab00;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 4.169,46.784 5.251,0 1.985,14.261 0.05,0 1.99,-14.261 5.247,0 -4.082,18.928 -6.363,0 -4.082,-18.928" id="path3143" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 19.356,46.784 11.216,0 0,4.031 -6.282,0 0,3.234 5.882,0 0,3.87 -5.882,0 0,3.763 6.517,0 0,4.03 -11.451,0 0,-18.928" id="path3145" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 54.93,52.247 0,-0.45 c 0,-1.117 -0.449,-2.043 -1.405,-2.043 -1.06,0 -1.538,0.823 -1.538,1.67 0,3.739 8.059,1.91 8.059,8.828 0,4.027 -2.357,5.832 -6.705,5.832 -4.082,0 -6.362,-1.404 -6.362,-5.331 l 0,-0.663 4.772,0 0,0.453 c 0,1.615 0.663,2.202 1.614,2.202 1.007,0 1.594,-0.798 1.594,-1.831 0,-3.739 -7.743,-1.88 -7.743,-8.586 0,-3.819 2.043,-5.913 6.205,-5.913 4.294,0 6.12,1.775 6.12,5.832 l -4.611,0" id="path3147" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 61.476,65.712 4.932,0 0,-18.928 -4.932,0 0,18.928 z" id="path3149" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 32.477,46.784 7.714,0 c 3.766,0 5.195,2.124 5.195,5.039 0,2.516 -0.979,4.16 -3.232,4.506 2.385,0.265 3.103,1.776 3.103,4.294 l 0,1.616 c 0,0.981 0,2.251 0.237,2.626 0.132,0.209 0.237,0.422 0.558,0.582 l 0,0.265 -5.25,0 C 40.325,64.705 40.325,62.9 40.325,62.109 l 0,-1.275 c 0,-2.147 -0.423,-2.704 -1.619,-2.704 l -1.296,0 0,7.582 -4.933,0 0,-18.928 z m 4.933,8.008 0.98,0 c 1.405,0 2.066,-0.9 2.066,-2.252 0,-1.541 -0.609,-2.202 -2.094,-2.202 l -0.952,0 0,4.454" id="path3151" style="fill:#1e1e1e;fill-opacity:1;fill-rule:evenodd;stroke:none"/>
|
||||
<path d="m 75.975,52.54 c 0,-2.147 -0.396,-2.786 -1.35,-2.786 -1.513,0 -1.67,1.381 -1.67,6.494 0,5.117 0.157,6.497 1.67,6.497 1.22,0 1.485,-1.063 1.485,-4.64 l 4.771,0 0,1.405 c 0,5.3 -3.101,6.574 -6.256,6.574 -5.54,0 -6.76,-2.784 -6.76,-9.836 0,-7.236 1.642,-9.833 6.76,-9.833 4.451,0 6.12,2.334 6.12,5.992 l 0,1.19 -4.77,0 0,-1.057" id="path3153" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 82.524,46.784 4.928,0 0,6.948 3.02,0 0,-6.948 4.932,0 0,18.928 -4.932,0 0,-7.793 -3.02,0 0,7.793 -4.928,0 0,-18.928" id="path3155" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 97.606,46.784 11.21,0 0,4.031 -6.282,0 0,3.234 5.886,0 0,3.87 -5.886,0 0,3.763 6.522,0 0,4.03 -11.45,0 0,-18.928" id="path3157" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 130.526,46.784 0,13.655 c 0,1.824 0.559,2.306 1.485,2.306 0.928,0 1.485,-0.482 1.485,-2.306 l 0,-13.655 4.929,0 0,12.408 c 0,5.298 -2.28,6.892 -6.414,6.892 -4.135,0 -6.412,-1.594 -6.412,-6.892 l 0,-12.408 4.927,0" id="path3159" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 140.415,46.784 5.51,0 3.185,11.665 0.05,0 0,-11.665 4.614,0 0,18.928 -5.407,0 -3.288,-11.689 -0.05,0 0,11.689 -4.613,0 0,-18.928" id="path3161" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 110.726,46.784 7.716,0 c 3.76,0 5.192,2.124 5.192,5.039 0,2.516 -0.979,4.16 -3.234,4.506 2.383,0.265 3.101,1.776 3.101,4.294 l 0,1.616 c 0,0.981 0,2.251 0.238,2.626 0.131,0.209 0.24,0.422 0.558,0.582 l 0,0.265 -5.251,0 c -0.477,-1.007 -0.477,-2.812 -0.477,-3.603 l 0,-1.275 c 0,-2.147 -0.422,-2.704 -1.612,-2.704 l -1.301,0 0,7.582 -4.93,0 0,-18.928 z m 4.93,8.008 0.979,0 c 1.404,0 2.07,-0.9 2.07,-2.252 0,-1.541 -0.61,-2.202 -2.095,-2.202 l -0.954,0 0,4.454" id="path3163" style="fill:#1e1e1e;fill-opacity:1;fill-rule:evenodd;stroke:none"/>
|
||||
<path d="m 162.334,55.428 6.361,0 0,10.284 -3.34,0 -0.113,-1.669 -0.05,0 c -0.662,1.617 -2.412,2.041 -4.082,2.041 -5.01,0 -5.459,-3.58 -5.459,-9.836 0,-6.336 1.22,-9.833 7.047,-9.833 3.502,0 5.993,1.775 5.993,6.36 l -4.771,0 c 0,-0.952 -0.07,-1.695 -0.266,-2.198 -0.185,-0.53 -0.554,-0.823 -1.139,-0.823 -1.612,0 -1.774,1.381 -1.774,6.494 0,5.117 0.161,6.497 1.67,6.497 1.034,0 1.639,-0.666 1.669,-3.978 l -1.75,0 0,-3.339" id="path3165" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 170.577,46.784 11.213,0 0,4.031 -6.28,0 0,3.234 5.885,0 0,3.87 -5.885,0 0,3.763 6.519,0 0,4.03 -11.452,0 0,-18.928" id="path3167" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
<path d="m 183.697,46.784 5.514,0 3.18,11.665 0.05,0 0,-11.665 4.615,0 0,18.928 -5.408,0 -3.284,-11.689 -0.05,0 0,11.689 -4.614,0 0,-18.928" id="path3169" style="fill:#1e1e1e;fill-opacity:1;fill-rule:nonzero;stroke:none"/>
|
||||
</svg>
|
||||
<!-- version: 20110311, original size: 197.056 66.084, border: 3% -->
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -6,14 +6,14 @@
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"flutterMode": "debug",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://localhost:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": [
|
||||
"--dart-define",
|
||||
"endpoint=http://192.168.1.3:8080",
|
||||
@@ -25,21 +25,21 @@
|
||||
"name": "Auth iOS Dev",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--dart-define", "endpoint=http://192.168.1.30:8080"]
|
||||
},
|
||||
{
|
||||
"name": "Auth iOS Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart"]
|
||||
},
|
||||
{
|
||||
"name": "Auth Android Prod",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"program": "mobile/apps/auth/lib/main.dart",
|
||||
"program": "auth/lib/main.dart",
|
||||
"args": ["--target", "lib/main.dart", "--flavor", "independent"]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,576 +0,0 @@
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/bootstrap.dart';
|
||||
import 'package:ente_auth/main.dart';
|
||||
import 'package:ente_auth/onboarding/view/common/add_chip.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Authentication Flow Integration Test with Persistence', () {
|
||||
testWidgets(
|
||||
'Complete auth flow: Use without backup -> Enter setup key -> Verify entry persists after restart',
|
||||
(WidgetTester tester) async {
|
||||
// Bootstrap the app
|
||||
await bootstrap(App.new);
|
||||
await init(false, via: 'integrationTest');
|
||||
await UpdateService.instance.init();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Step 1: Click on "Use without backup" option
|
||||
final useOfflineText = find.text('Use without backups');
|
||||
expect(
|
||||
useOfflineText,
|
||||
findsOneWidget,
|
||||
reason:
|
||||
'ERROR: "Use without backups" button not found on initial screen. Check if app loaded correctly or text changed.',
|
||||
);
|
||||
await tester.tap(useOfflineText);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Step 2: Click OK button on the warning dialog
|
||||
final okButton = find.text('Ok');
|
||||
expect(
|
||||
okButton,
|
||||
findsOneWidget,
|
||||
reason:
|
||||
'ERROR: "Ok" button not found in warning dialog. Check if dialog appeared or button text changed.',
|
||||
);
|
||||
await tester.tap(okButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Wait for navigation to complete
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Step 3: Navigate to manual entry screen
|
||||
bool foundManualEntry = false;
|
||||
|
||||
// Try FloatingActionButton approach first
|
||||
final fabFinder = find.byType(FloatingActionButton);
|
||||
if (fabFinder.evaluate().isNotEmpty) {
|
||||
await tester.tap(fabFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final manualEntryFinder = find.text('Enter a setup key');
|
||||
if (manualEntryFinder.evaluate().isNotEmpty) {
|
||||
await tester.tap(manualEntryFinder);
|
||||
await tester.pumpAndSettle();
|
||||
foundManualEntry = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative approaches if FAB didn't work
|
||||
if (!foundManualEntry) {
|
||||
final alternatives = [
|
||||
'Enter details manually',
|
||||
'Enter a setup key',
|
||||
];
|
||||
|
||||
for (final text in alternatives) {
|
||||
final finder = find.text(text);
|
||||
if (finder.evaluate().isNotEmpty) {
|
||||
await tester.tap(finder.first);
|
||||
await tester.pumpAndSettle();
|
||||
foundManualEntry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect(
|
||||
foundManualEntry,
|
||||
isTrue,
|
||||
reason:
|
||||
'ERROR: Could not find manual entry option. Tried FAB + "Enter details manually", "Enter a setup key", etc. Check UI navigation.',
|
||||
);
|
||||
|
||||
// Step 4: Fill in the form with test data
|
||||
final textFields = find.byType(TextFormField);
|
||||
expect(
|
||||
textFields.evaluate().length,
|
||||
greaterThanOrEqualTo(3),
|
||||
reason:
|
||||
'ERROR: Expected at least 3 text fields (issuer, secret, account) but found ${textFields.evaluate().length}. Check manual entry form.',
|
||||
);
|
||||
|
||||
// Fill issuer field
|
||||
await tester.tap(textFields.first);
|
||||
await tester.enterText(textFields.first, 'testIssuer');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Fill secret field
|
||||
await tester.tap(textFields.at(1));
|
||||
await tester.enterText(
|
||||
textFields.at(1),
|
||||
'JBSWY3DPEHPK3PXP',
|
||||
); // Valid base32 secret
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Fill account field
|
||||
await tester.tap(textFields.at(2));
|
||||
await tester.enterText(textFields.at(2), 'testAccount');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Step 5: Save the entry
|
||||
final saveButton = find.text('Save');
|
||||
expect(
|
||||
saveButton,
|
||||
findsOneWidget,
|
||||
reason:
|
||||
'ERROR: "Save" button not found on manual entry form. Check if button text changed or form layout changed.',
|
||||
);
|
||||
await tester.tap(saveButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Step 6: Verify entry was created successfully
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
// Check if coach mark overlay is present and dismiss it
|
||||
final coachMarkOverlay = find.text('Ok');
|
||||
if (coachMarkOverlay.evaluate().isNotEmpty) {
|
||||
print('🎯 Dismissing coach mark overlay...');
|
||||
await tester.tap(coachMarkOverlay);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
// Look for the created entry
|
||||
final issuerEntryFinder = find.textContaining('testIssuer');
|
||||
expect(
|
||||
issuerEntryFinder,
|
||||
findsAtLeastNWidgets(1),
|
||||
reason:
|
||||
'ERROR: testIssuer entry not found after saving. Entry creation may have failed or navigation issue occurred.',
|
||||
);
|
||||
|
||||
final accountEntryFinder = find.textContaining('testAccount');
|
||||
expect(
|
||||
accountEntryFinder,
|
||||
findsAtLeastNWidgets(1),
|
||||
reason:
|
||||
'ERROR: testAccount not found after saving. Account field may not have been saved properly.',
|
||||
);
|
||||
print('✅ Step 1 completed: Entry created successfully');
|
||||
print('- testIssuer entry is visible');
|
||||
print('- testAccount is visible');
|
||||
|
||||
// warning about clearing
|
||||
|
||||
// Step 8: Add second code entry
|
||||
print('🔄 Adding second code entry...');
|
||||
|
||||
// Wait a moment before adding second entry
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
// Click FAB to add second entry
|
||||
final fabFinder2 = find.byType(FloatingActionButton);
|
||||
expect(
|
||||
fabFinder2,
|
||||
findsOneWidget,
|
||||
reason: 'FAB not found for second entry',
|
||||
);
|
||||
await tester.tap(fabFinder2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Click "Enter details manually" (coach mark won't show second time)
|
||||
final manualEntryFinder = find.text('Enter details manually');
|
||||
expect(
|
||||
manualEntryFinder,
|
||||
findsOneWidget,
|
||||
reason: 'Manual entry option not found',
|
||||
);
|
||||
await tester.tap(manualEntryFinder);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Fill second entry form
|
||||
final textFields2 = find.byType(TextFormField);
|
||||
expect(textFields2.evaluate().length, greaterThanOrEqualTo(3));
|
||||
|
||||
// Fill second issuer field
|
||||
await tester.tap(textFields2.first);
|
||||
await tester.enterText(textFields2.first, 'testIssuer2');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify issuer field was filled
|
||||
final issuerField = tester.widget<TextFormField>(textFields2.first);
|
||||
print(
|
||||
'✓ Issuer field controller text: "${issuerField.controller?.text ?? "null"}"');
|
||||
|
||||
// Fill second secret field
|
||||
await tester.tap(textFields2.at(1));
|
||||
await tester.enterText(textFields2.at(1), 'JBSWY3DPEHPK3PXP');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify secret field was filled
|
||||
final secretField = tester.widget<TextFormField>(textFields2.at(1));
|
||||
print(
|
||||
'✓ Secret field controller text: "${secretField.controller?.text ?? "null"}"');
|
||||
|
||||
// Fill second account field
|
||||
await tester.tap(textFields2.at(2));
|
||||
await tester.enterText(textFields2.at(2), 'testAccount2');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Save second entry
|
||||
final saveButton2 = find.text('Save');
|
||||
await tester.tap(saveButton2);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Verify both entries exist
|
||||
final issuer1Finder = find.textContaining('testIssuer');
|
||||
final issuer2Finder = find.textContaining('testIssuer2');
|
||||
final account1Finder = find.textContaining('testAccount');
|
||||
final account2Finder = find.textContaining('testAccount2');
|
||||
|
||||
expect(issuer1Finder, findsAtLeastNWidgets(1),
|
||||
reason: 'First issuer not found');
|
||||
expect(issuer2Finder, findsAtLeastNWidgets(1),
|
||||
reason: 'Second issuer not found');
|
||||
expect(
|
||||
account1Finder,
|
||||
findsAtLeastNWidgets(1),
|
||||
reason: 'First account not found',
|
||||
);
|
||||
expect(
|
||||
account2Finder,
|
||||
findsAtLeastNWidgets(1),
|
||||
reason: 'Second account not found',
|
||||
);
|
||||
|
||||
print('✅ Step 2 completed: Both entries created successfully');
|
||||
print('- testIssuer and testIssuer2 entries are visible');
|
||||
print('- testAccount and testAccount2 are visible');
|
||||
|
||||
// Step 9: Test search functionality
|
||||
print('🔍 Testing search functionality...');
|
||||
|
||||
// Click on search icon to activate search
|
||||
final searchIcon = find.byIcon(Icons.search);
|
||||
expect(searchIcon, findsOneWidget, reason: 'Search icon not found');
|
||||
await tester.tap(searchIcon);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Find the search text field
|
||||
final searchField = find.byType(TextField);
|
||||
expect(searchField, findsOneWidget,
|
||||
reason: 'Search text field not found');
|
||||
|
||||
// Enter search term "issuer2"
|
||||
await tester.tap(searchField);
|
||||
await tester.enterText(searchField, 'issuer2');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify only one result is shown (testIssuer2)
|
||||
final searchResults = find.textContaining('testIssuer');
|
||||
final issuer2Results = find.textContaining('testIssuer2');
|
||||
|
||||
// Should find testIssuer2 but not testIssuer when searching for "issuer2"
|
||||
expect(issuer2Results, findsAtLeastNWidgets(1),
|
||||
reason: 'testIssuer2 not found in search results');
|
||||
|
||||
// Verify total results - should only show the matching entry
|
||||
final allVisibleIssuers = find.textContaining('testIssuer');
|
||||
expect(allVisibleIssuers.evaluate().length, equals(1),
|
||||
reason: 'Search should show only one result for "issuer2"');
|
||||
|
||||
print('✅ Search results verified: only testIssuer2 is visible');
|
||||
|
||||
// Clear search bar
|
||||
final clearIcon = find.byIcon(Icons.clear);
|
||||
expect(clearIcon, findsOneWidget,
|
||||
reason: 'Clear search icon not found');
|
||||
await tester.tap(clearIcon);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify both entries are visible again after clearing search
|
||||
final allIssuer1 = find.textContaining('testIssuer');
|
||||
final allIssuer2 = find.textContaining('testIssuer2');
|
||||
expect(allIssuer1, findsAtLeastNWidgets(1),
|
||||
reason: 'testIssuer not visible after clearing search');
|
||||
expect(allIssuer2, findsAtLeastNWidgets(1),
|
||||
reason: 'testIssuer2 not visible after clearing search');
|
||||
|
||||
print('✅ Search cleared: both entries visible again');
|
||||
print('✅ Step 3 completed: Search functionality working correctly');
|
||||
|
||||
// Step 10: Long press on issuer2 to edit and add tags
|
||||
print('🏷️ Testing tag functionality...');
|
||||
|
||||
// Long press on testIssuer2 entry to bring up edit menu
|
||||
final issuer2Entry = find.textContaining('testIssuer2');
|
||||
expect(issuer2Entry, findsOneWidget,
|
||||
reason: 'testIssuer2 entry not found for long press');
|
||||
await tester.longPress(issuer2Entry);
|
||||
await tester.pumpAndSettle();
|
||||
LocalAuthenticationService.instance.lastAuthTime = DateTime.now()
|
||||
.add(const Duration(minutes: 10))
|
||||
.millisecondsSinceEpoch;
|
||||
|
||||
// Look for edit option and tap it
|
||||
final editOption = find.text('Edit');
|
||||
expect(editOption, findsOneWidget, reason: 'Edit option not found');
|
||||
await tester.tap(editOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Wait for edit page to load
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
// Look for AddChip widget to add first tag
|
||||
final addChip = find.byType(AddChip);
|
||||
expect(addChip, findsOneWidget, reason: 'AddChip widget not found');
|
||||
await tester.tap(addChip);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter first tag name "tag1"
|
||||
final tagInputField = find.byType(TextField).last;
|
||||
await tester.tap(tagInputField);
|
||||
await tester.enterText(tagInputField, 'tag1');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap create/save button for first tag
|
||||
final createButton = find.text('Create');
|
||||
expect(createButton, findsOneWidget,
|
||||
reason: 'Create button not found for first tag');
|
||||
await tester.tap(createButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Add second tag
|
||||
final addChip2 = find.byType(AddChip);
|
||||
await tester.tap(addChip2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Enter second tag name "tag2"
|
||||
final tagInputField2 = find.byType(TextField).last;
|
||||
await tester.tap(tagInputField2);
|
||||
await tester.enterText(tagInputField2, 'tag2');
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap create button for second tag
|
||||
final createButton2 = find.text('Create');
|
||||
await tester.tap(createButton2);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify tags are selected/visible
|
||||
final tag1Chip = find.text('tag1');
|
||||
final tag2Chip = find.text('tag2');
|
||||
expect(tag1Chip, findsOneWidget,
|
||||
reason: 'tag1 not found after creation');
|
||||
expect(tag2Chip, findsOneWidget,
|
||||
reason: 'tag2 not found after creation');
|
||||
|
||||
print('✅ Tags created: tag1 and tag2 are visible');
|
||||
|
||||
// Save the edited entry
|
||||
final saveEditButton = find.text('Save');
|
||||
expect(saveEditButton, findsOneWidget,
|
||||
reason: 'Save button not found on edit page');
|
||||
await tester.tap(saveEditButton);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Wait for navigation back to home
|
||||
await tester.pumpAndSettle(const Duration(seconds: 2));
|
||||
|
||||
print('✅ Entry saved with tags');
|
||||
|
||||
// Step 11: Test tag filtering functionality
|
||||
print('🏷️ Testing tag filtering...');
|
||||
|
||||
// Click on tag1 to filter entries
|
||||
final tag1Filter = find.textContaining('tag1');
|
||||
if (tag1Filter.evaluate().isNotEmpty) {
|
||||
await tester.tap(tag1Filter.first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify only testIssuer2 is visible (the one with tag1)
|
||||
final filteredIssuer2 = find.textContaining('testIssuer2');
|
||||
final filteredIssuer1 = find.textContaining('testIssuer').evaluate().where(
|
||||
(element) => !element.widget.toString().contains('testIssuer2')
|
||||
).length;
|
||||
|
||||
expect(filteredIssuer2, findsAtLeastNWidgets(1),
|
||||
reason: 'testIssuer2 not visible when filtering by tag1');
|
||||
expect(filteredIssuer1, equals(0),
|
||||
reason: 'testIssuer should not be visible when filtering by tag1');
|
||||
|
||||
print('✅ Tag1 filtering verified: only testIssuer2 is visible');
|
||||
|
||||
// Click "All" to clear tag filter
|
||||
final allFilter = find.text('All');
|
||||
if (allFilter.evaluate().isNotEmpty) {
|
||||
await tester.tap(allFilter);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify both entries are visible again
|
||||
final allEntriesIssuer1 = find.textContaining('testIssuer');
|
||||
final allEntriesIssuer2 = find.textContaining('testIssuer2');
|
||||
expect(allEntriesIssuer1, findsAtLeastNWidgets(1));
|
||||
expect(allEntriesIssuer2, findsAtLeastNWidgets(1));
|
||||
|
||||
print('✅ Tag filter cleared: both entries visible again');
|
||||
}
|
||||
}
|
||||
|
||||
print('✅ Step 4 completed: Tag functionality working correctly');
|
||||
|
||||
// Step 12: Test trash functionality
|
||||
print('🗑️ Testing trash functionality...');
|
||||
|
||||
// Long press on testIssuer2 entry to bring up context menu
|
||||
final issuer2EntryForTrash = find.textContaining('testIssuer2').first;
|
||||
await tester.longPress(issuer2EntryForTrash);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Look for trash/delete option and tap it
|
||||
final trashOption = find.text('Trash');
|
||||
if (trashOption.evaluate().isEmpty) {
|
||||
// Try alternative delete options
|
||||
final deleteOption = find.text('Delete');
|
||||
expect(deleteOption, findsOneWidget, reason: 'Delete/Trash option not found');
|
||||
await tester.tap(deleteOption);
|
||||
} else {
|
||||
await tester.tap(trashOption);
|
||||
}
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Confirm deletion if dialog appears
|
||||
final confirmButtons = [
|
||||
find.text('Yes'),
|
||||
find.text('OK'),
|
||||
find.text('Confirm'),
|
||||
find.text('Delete'),
|
||||
find.text('Trash'),
|
||||
];
|
||||
|
||||
for (final button in confirmButtons) {
|
||||
if (button.evaluate().isNotEmpty) {
|
||||
await tester.tap(button);
|
||||
await tester.pumpAndSettle();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
print('✅ Issuer2 entry trashed');
|
||||
|
||||
// Step 13: Verify tags are no longer visible and trash tag appears
|
||||
print('🏷️ Verifying tag visibility after trash...');
|
||||
|
||||
// Wait for UI to update
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
// Verify tag1 and tag2 are no longer visible (since issuer2 was the only entry with these tags)
|
||||
final tag1Visible = find.textContaining('tag1');
|
||||
final tag2Visible = find.textContaining('tag2');
|
||||
|
||||
// Tags should not be visible anymore since the only entry with these tags was trashed
|
||||
expect(tag1Visible.evaluate().isEmpty, isTrue, reason: 'tag1 should not be visible after trashing issuer2');
|
||||
expect(tag2Visible.evaluate().isEmpty, isTrue, reason: 'tag2 should not be visible after trashing issuer2');
|
||||
|
||||
// Verify trash tag is now visible
|
||||
final trashTag = find.text('Trash');
|
||||
expect(trashTag, findsOneWidget, reason: 'Trash tag not visible after trashing entry');
|
||||
|
||||
print('✅ Tags hidden and Trash tag visible');
|
||||
|
||||
// Step 14: Test trash filtering
|
||||
print('🗑️ Testing trash tag filtering...');
|
||||
|
||||
// Click on Trash tag to show trashed items
|
||||
await tester.tap(trashTag);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify issuer2 is visible in trash
|
||||
final trashedIssuer2 = find.textContaining('testIssuer2');
|
||||
expect(trashedIssuer2, findsOneWidget, reason: 'testIssuer2 not visible in trash');
|
||||
|
||||
// Verify issuer1 is not visible (should be filtered out)
|
||||
final issuer1InTrash = find.textContaining('testIssuer').evaluate().where(
|
||||
(element) => !element.widget.toString().contains('testIssuer2')
|
||||
).length;
|
||||
expect(issuer1InTrash, equals(0), reason: 'testIssuer should not be visible in trash filter');
|
||||
|
||||
print('✅ Trash filtering working: only trashed items visible');
|
||||
|
||||
// Step 15: Test All filter (should not show trashed items)
|
||||
print('📋 Testing All filter excludes trash...');
|
||||
|
||||
// Click on "All" to show all non-trashed items
|
||||
final allTag = find.text('All');
|
||||
await tester.tap(allTag);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify issuer1 is visible
|
||||
final allFilterIssuer1 = find.textContaining('testIssuer').evaluate().where(
|
||||
(element) => !element.widget.toString().contains('testIssuer2')
|
||||
).length;
|
||||
expect(allFilterIssuer1, greaterThan(0), reason: 'testIssuer should be visible in All filter');
|
||||
|
||||
// Verify issuer2 is NOT visible in All
|
||||
final allFilterIssuer2 = find.textContaining('testIssuer2');
|
||||
expect(allFilterIssuer2.evaluate().isEmpty, isTrue, reason: 'testIssuer2 should not be visible in All filter');
|
||||
|
||||
print('✅ All filter working: trashed items excluded');
|
||||
|
||||
// Step 16: Test restore functionality
|
||||
print('♻️ Testing restore functionality...');
|
||||
|
||||
// Go back to trash view
|
||||
await tester.tap(trashTag);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Long press on trashed issuer2 entry
|
||||
final trashedEntryForRestore = find.textContaining('testIssuer2');
|
||||
await tester.longPress(trashedEntryForRestore);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Look for restore option
|
||||
final restoreOption = find.text('Restore');
|
||||
expect(restoreOption, findsOneWidget, reason: 'Restore option not found');
|
||||
await tester.tap(restoreOption);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
print('✅ Restore option tapped');
|
||||
|
||||
// Step 17: Verify restoration worked
|
||||
print('✅ Verifying restoration...');
|
||||
|
||||
// Wait for restoration to complete
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
// Go to All view to check if issuer2 is restored
|
||||
await tester.tap(allTag);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Verify both entries are now visible in All
|
||||
final restoredIssuer1 = find.textContaining('testIssuer');
|
||||
final restoredIssuer2 = find.textContaining('testIssuer2');
|
||||
|
||||
expect(restoredIssuer1, findsAtLeastNWidgets(1), reason: 'testIssuer not visible after restore');
|
||||
expect(restoredIssuer2, findsAtLeastNWidgets(1), reason: 'testIssuer2 not visible after restore');
|
||||
|
||||
// Verify tags are visible again
|
||||
final restoredTag1 = find.textContaining('tag1');
|
||||
final restoredTag2 = find.textContaining('tag2');
|
||||
|
||||
if (restoredTag1.evaluate().isNotEmpty && restoredTag2.evaluate().isNotEmpty) {
|
||||
print('✅ Tags restored and visible again');
|
||||
}
|
||||
|
||||
print('✅ Step 5 completed: Trash and restore functionality working correctly');
|
||||
|
||||
print('✅ Integration test completed successfully!');
|
||||
print('- Both entries created and verified');
|
||||
print('- Search functionality tested and working');
|
||||
print('- Tag functionality tested and working');
|
||||
print('- Trash functionality tested and working');
|
||||
print('- Restore functionality tested and working');
|
||||
print('- Multiple TOTP codes are being generated');
|
||||
print('- Data persistence is working correctly');
|
||||
},
|
||||
timeout: const Timeout(Duration(minutes: 3)),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ PODS:
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- cupertino_http (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -39,8 +40,6 @@ PODS:
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- ente_qr (0.0.1):
|
||||
- Flutter
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
@@ -62,14 +61,13 @@ PODS:
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
- Flutter
|
||||
- flutter_native_splash (2.4.3):
|
||||
- flutter_native_splash (0.0.1):
|
||||
- Flutter
|
||||
- flutter_secure_storage (6.0.0):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- Toast
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
@@ -89,9 +87,9 @@ PODS:
|
||||
- qr_code_scanner (0.2.0):
|
||||
- Flutter
|
||||
- MTBBarcodeScanner
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- SDWebImage (5.21.0):
|
||||
- SDWebImage/Core (= 5.21.0)
|
||||
- SDWebImage/Core (5.21.0)
|
||||
- Sentry/HybridSDK (8.46.0)
|
||||
- sentry_flutter (8.14.2):
|
||||
- Flutter
|
||||
@@ -104,43 +102,37 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- sodium_libs (2.2.1):
|
||||
- Flutter
|
||||
- sqflite_darwin (0.0.4):
|
||||
- sqflite (0.0.3):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (3.50.2):
|
||||
- sqlite3/common (= 3.50.2)
|
||||
- sqlite3/common (3.50.2)
|
||||
- sqlite3/dbstatvtab (3.50.2):
|
||||
- "sqlite3 (3.46.1+1)":
|
||||
- "sqlite3/common (= 3.46.1+1)"
|
||||
- "sqlite3/common (3.46.1+1)"
|
||||
- "sqlite3/dbstatvtab (3.46.1+1)":
|
||||
- sqlite3/common
|
||||
- sqlite3/fts5 (3.50.2):
|
||||
- "sqlite3/fts5 (3.46.1+1)":
|
||||
- sqlite3/common
|
||||
- sqlite3/math (3.50.2):
|
||||
- "sqlite3/perf-threadsafe (3.46.1+1)":
|
||||
- sqlite3/common
|
||||
- sqlite3/perf-threadsafe (3.50.2):
|
||||
- sqlite3/common
|
||||
- sqlite3/rtree (3.50.2):
|
||||
- "sqlite3/rtree (3.46.1+1)":
|
||||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sqlite3 (~> 3.50.1)
|
||||
- "sqlite3 (~> 3.46.0+1)"
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/math
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
- SwiftyGif (5.4.5)
|
||||
- ua_client_hints (1.4.1):
|
||||
- Flutter
|
||||
- Toast (4.1.1)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- app_links (from `.symlinks/plugins/app_links/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
|
||||
- cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- ente_qr (from `.symlinks/plugins/ente_qr/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`)
|
||||
@@ -152,7 +144,6 @@ DEPENDENCIES:
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
|
||||
- objective_c (from `.symlinks/plugins/objective_c/ios`)
|
||||
@@ -164,9 +155,8 @@ DEPENDENCIES:
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||
- ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
- sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -179,18 +169,17 @@ SPEC REPOS:
|
||||
- Sentry
|
||||
- sqlite3
|
||||
- SwiftyGif
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
app_links:
|
||||
:path: ".symlinks/plugins/app_links/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
cupertino_http:
|
||||
:path: ".symlinks/plugins/cupertino_http/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
ente_qr:
|
||||
:path: ".symlinks/plugins/ente_qr/ios"
|
||||
file_picker:
|
||||
:path: ".symlinks/plugins/file_picker/ios"
|
||||
file_saver:
|
||||
@@ -213,8 +202,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_secure_storage/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
move_to_background:
|
||||
@@ -237,55 +224,51 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sodium_libs:
|
||||
:path: ".symlinks/plugins/sodium_libs/ios"
|
||||
sqflite_darwin:
|
||||
:path: ".symlinks/plugins/sqflite_darwin/darwin"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/darwin"
|
||||
sqlite3_flutter_libs:
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/darwin"
|
||||
ua_client_hints:
|
||||
:path: ".symlinks/plugins/ua_client_hints/ios"
|
||||
:path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: f3e17e4ee5e357b39d8b95290a9b2c299fca71c6
|
||||
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
ente_qr: f39434aa69ea0e71047b49316365b2737f8a20aa
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: e03bdda7637bcd3539bfe718fddd980e9508efaa
|
||||
flutter_email_sender: 10a22605f92809a11ef52b2f412db806c6082d40
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
|
||||
flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
|
||||
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sodium_libs: 1faae17af662384acbd13e41867a0008cd2e2318
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
sqlite3: 3e82a2daae39ba3b41ae6ee84a130494585460fc
|
||||
sqlite3_flutter_libs: 2c48c4ee7217fd653251975e43412250d5bcbbe2
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38
|
||||
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
|
||||
PODFILE CHECKSUM: 78f002751f1a8f65042b8da97902ba4124271c5a
|
||||
|
||||
@@ -2,21 +2,20 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import "package:ente_auth/onboarding/view/onboarding_page.dart";
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/signed_in_event.dart';
|
||||
import 'package:ente_events/models/signed_out_event.dart';
|
||||
import 'package:ente_strings/l10n/strings_localizations.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
@@ -132,7 +131,6 @@ class _AppState extends State<App>
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
StringsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
@@ -152,7 +150,6 @@ class _AppState extends State<App>
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
StringsLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
|
||||
@@ -1,36 +1,95 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ente_base/models/database.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/signed_out_event.dart';
|
||||
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/directory_utils.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration extends BaseConfiguration {
|
||||
class Configuration {
|
||||
Configuration._privateConstructor();
|
||||
|
||||
static final Configuration instance = Configuration._privateConstructor();
|
||||
static const endpoint = String.fromEnvironment(
|
||||
"endpoint",
|
||||
defaultValue: kDefaultProductionEndpoint,
|
||||
);
|
||||
static const emailKey = "email";
|
||||
static const keyAttributesKey = "key_attributes";
|
||||
|
||||
static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
|
||||
static const keyKey = "key";
|
||||
static const secretKeyKey = "secret_key";
|
||||
static const authSecretKeyKey = "auth_secret_key";
|
||||
static const offlineAuthSecretKey = "offline_auth_secret_key";
|
||||
static const tokenKey = "token";
|
||||
static const encryptedTokenKey = "encrypted_token";
|
||||
static const userIDKey = "user_id";
|
||||
static const hasMigratedSecureStorageKey = "has_migrated_secure_storage";
|
||||
static const hasOptedForOfflineModeKey = "has_opted_for_offline_mode";
|
||||
static const endPointKey = "endpoint";
|
||||
final List<String> onlineSecureKeys = [
|
||||
keyKey,
|
||||
secretKeyKey,
|
||||
authSecretKeyKey,
|
||||
];
|
||||
|
||||
final kTempFolderDeletionTimeBuffer = const Duration(days: 1).inMicroseconds;
|
||||
|
||||
static final _logger = Logger("Configuration");
|
||||
|
||||
String? _cachedToken;
|
||||
late SharedPreferences _preferences;
|
||||
String? _key;
|
||||
String? _secretKey;
|
||||
String? _authSecretKey;
|
||||
String? _offlineAuthKey;
|
||||
late FlutterSecureStorage _secureStorage;
|
||||
late String _tempDirectory;
|
||||
|
||||
@override
|
||||
Future<void> init(List<EnteBaseDatabase> dbs) async {
|
||||
await super.init(dbs);
|
||||
String? _volatilePassword;
|
||||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage(
|
||||
iOptions: IOSOptions(
|
||||
accessibility: KeychainAccessibility.first_unlock_this_device,
|
||||
),
|
||||
);
|
||||
sqfliteFfiInit();
|
||||
_tempDirectory = (await DirectoryUtils.getDirectoryForInit()).path;
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
if (tempDirectory.existsSync() &&
|
||||
(_preferences.getInt(lastTempFolderClearTimeKey) ?? 0) <
|
||||
(currentTime - kTempFolderDeletionTimeBuffer)) {
|
||||
await tempDirectory.delete(recursive: true);
|
||||
await _preferences.setInt(lastTempFolderClearTimeKey, currentTime);
|
||||
_logger.info("Cleared temp folder");
|
||||
} else {
|
||||
_logger.info("Skipping temp folder clear");
|
||||
}
|
||||
} catch (e) {
|
||||
_logger.warning(e);
|
||||
}
|
||||
tempDirectory.createSync(recursive: true);
|
||||
await _initOnlineAccount();
|
||||
await _initOfflineAccount();
|
||||
}
|
||||
|
||||
@@ -40,10 +99,303 @@ class Configuration extends BaseConfiguration {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> _initOnlineAccount() async {
|
||||
if (!_preferences.containsKey(tokenKey)) {
|
||||
for (final key in onlineSecureKeys) {
|
||||
unawaited(
|
||||
_secureStorage.delete(
|
||||
key: key,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_key = await _secureStorage.read(
|
||||
key: keyKey,
|
||||
);
|
||||
_secretKey = await _secureStorage.read(
|
||||
key: secretKeyKey,
|
||||
);
|
||||
_authSecretKey = await _secureStorage.read(
|
||||
key: authSecretKeyKey,
|
||||
);
|
||||
if (_key == null) {
|
||||
await logout(autoLogout: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout({bool autoLogout = false}) async {
|
||||
await _preferences.clear();
|
||||
for (String key in onlineSecureKeys) {
|
||||
await _secureStorage.delete(
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
await AuthenticatorDB.instance.clearTable();
|
||||
_key = null;
|
||||
_cachedToken = null;
|
||||
_secretKey = null;
|
||||
_authSecretKey = null;
|
||||
await super.logout();
|
||||
Bus.instance.fire(SignedOutEvent());
|
||||
}
|
||||
|
||||
Future<KeyGenResult> generateKey(String password) async {
|
||||
// Create a master key
|
||||
final masterKey = CryptoUtil.generateKey();
|
||||
|
||||
// Create a recovery key
|
||||
final recoveryKey = CryptoUtil.generateKey();
|
||||
|
||||
// Encrypt master key and recovery key with each other
|
||||
final encryptedMasterKey = CryptoUtil.encryptSync(masterKey, recoveryKey);
|
||||
final encryptedRecoveryKey = CryptoUtil.encryptSync(recoveryKey, masterKey);
|
||||
|
||||
// Derive a key from the password that will be used to encrypt and
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
||||
// Encrypt the key with this derived key
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
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,
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
|
||||
Future<Tuple2<KeyAttributes, Uint8List>> getAttributesForNewPassword(
|
||||
String password,
|
||||
) async {
|
||||
// Get master key
|
||||
final masterKey = getKey();
|
||||
|
||||
// Derive a key from the password that will be used to encrypt and
|
||||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
||||
// Encrypt the key with this derived key
|
||||
final encryptedKeyData =
|
||||
CryptoUtil.encryptSync(masterKey!, derivedKeyResult.key);
|
||||
|
||||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
return Tuple2(updatedAttributes, loginKey);
|
||||
}
|
||||
|
||||
// decryptSecretsAndGetLoginKey decrypts the master key and recovery key
|
||||
// with the given password and save them in local secure storage.
|
||||
// This method also returns the keyEncKey that can be used for performing
|
||||
// SRP setup for existing users.
|
||||
Future<Uint8List> decryptSecretsAndGetKeyEncKey(
|
||||
String password,
|
||||
KeyAttributes attributes, {
|
||||
Uint8List? keyEncryptionKey,
|
||||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
|
||||
_logger.info('user-key done');
|
||||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
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(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
|
||||
Future<void> recover(String recoveryKey) async {
|
||||
// check if user has entered mnemonic code
|
||||
if (recoveryKey.contains(' ')) {
|
||||
final split = recoveryKey.split(' ');
|
||||
if (split.length != mnemonicKeyWordCount) {
|
||||
String wordThatIsFollowedByEmptySpaceInSplit = '';
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
String word = split[i];
|
||||
if (word.isEmpty) {
|
||||
wordThatIsFollowedByEmptySpaceInSplit =
|
||||
'\n\nExtra space after word at position $i';
|
||||
break;
|
||||
}
|
||||
}
|
||||
throw AssertionError(
|
||||
'\nRecovery code should have $mnemonicKeyWordCount words, '
|
||||
'found ${split.length} words instead.$wordThatIsFollowedByEmptySpaceInSplit',
|
||||
);
|
||||
}
|
||||
recoveryKey = bip39.mnemonicToEntropy(recoveryKey);
|
||||
}
|
||||
final attributes = getKeyAttributes();
|
||||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
String getHttpEndpoint() {
|
||||
return _preferences.getString(endPointKey) ?? endpoint;
|
||||
}
|
||||
|
||||
Future<void> setHttpEndpoint(String endpoint) async {
|
||||
await _preferences.setString(endPointKey, endpoint);
|
||||
Bus.instance.fire(EndpointUpdatedEvent());
|
||||
}
|
||||
|
||||
String? getToken() {
|
||||
_cachedToken ??= _preferences.getString(tokenKey);
|
||||
return _cachedToken;
|
||||
}
|
||||
|
||||
bool isLoggedIn() {
|
||||
return getToken() != null;
|
||||
}
|
||||
|
||||
Future<void> setToken(String token) async {
|
||||
_cachedToken = token;
|
||||
await _preferences.setString(tokenKey, token);
|
||||
Bus.instance.fire(SignedInEvent());
|
||||
}
|
||||
|
||||
Future<void> setEncryptedToken(String encryptedToken) async {
|
||||
await _preferences.setString(encryptedTokenKey, encryptedToken);
|
||||
}
|
||||
|
||||
String? getEncryptedToken() {
|
||||
return _preferences.getString(encryptedTokenKey);
|
||||
}
|
||||
|
||||
String? getEmail() {
|
||||
return _preferences.getString(emailKey);
|
||||
}
|
||||
|
||||
Future<void> setEmail(String email) async {
|
||||
await _preferences.setString(emailKey, email);
|
||||
}
|
||||
|
||||
int? getUserID() {
|
||||
return _preferences.getInt(userIDKey);
|
||||
}
|
||||
|
||||
Future<void> setUserID(int userID) async {
|
||||
await _preferences.setInt(userIDKey, userID);
|
||||
}
|
||||
|
||||
Future<void> setKeyAttributes(KeyAttributes attributes) async {
|
||||
await _preferences.setString(keyAttributesKey, attributes.toJson());
|
||||
}
|
||||
|
||||
KeyAttributes? getKeyAttributes() {
|
||||
final jsonValue = _preferences.getString(keyAttributesKey);
|
||||
if (jsonValue == null) {
|
||||
return null;
|
||||
} else {
|
||||
return KeyAttributes.fromJson(jsonValue);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setKey(String key) async {
|
||||
_key = key;
|
||||
await _secureStorage.write(
|
||||
key: keyKey,
|
||||
value: key,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setSecretKey(String? secretKey) async {
|
||||
_secretKey = secretKey;
|
||||
await _secureStorage.write(
|
||||
key: secretKeyKey,
|
||||
value: secretKey,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setAuthSecretKey(String? authSecretKey) async {
|
||||
@@ -54,6 +406,14 @@ class Configuration extends BaseConfiguration {
|
||||
);
|
||||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
@@ -66,6 +426,24 @@ class Configuration extends BaseConfiguration {
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
// Caution: This directory is cleared on app start
|
||||
String getTempDirectory() {
|
||||
return _tempDirectory;
|
||||
}
|
||||
|
||||
bool hasConfiguredAccount() {
|
||||
return getToken() != null && _key != null;
|
||||
}
|
||||
|
||||
bool hasOptedForOfflineMode() {
|
||||
return _preferences.getBool(hasOptedForOfflineModeKey) ?? false;
|
||||
}
|
||||
@@ -86,4 +464,16 @@ class Configuration extends BaseConfiguration {
|
||||
}
|
||||
await _preferences.setBool(hasOptedForOfflineModeKey, true);
|
||||
}
|
||||
|
||||
void setVolatilePassword(String volatilePassword) {
|
||||
_volatilePassword = volatilePassword;
|
||||
}
|
||||
|
||||
void resetVolatilePassword() {
|
||||
_volatilePassword = null;
|
||||
}
|
||||
|
||||
String? getVolatilePassword() {
|
||||
return _volatilePassword;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ import 'dart:collection';
|
||||
import 'dart:core';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_logging/tunneled_transport.dart';
|
||||
import 'package:ente_auth/core/logging/tunneled_transport.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart' as log;
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -16,9 +17,6 @@ import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// Type definition for functions that may return a value or Future
|
||||
typedef FutureOrVoidCallback = dynamic Function();
|
||||
|
||||
extension SuperString on String {
|
||||
Iterable<String> chunked(int chunkSize) sync* {
|
||||
var start = 0;
|
||||
@@ -36,7 +34,7 @@ extension SuperString on String {
|
||||
}
|
||||
}
|
||||
|
||||
extension SuperLogRecord on log.LogRecord {
|
||||
extension SuperLogRecord on LogRecord {
|
||||
String toPrettyString([String? extraLines]) {
|
||||
final header = "[$loggerName] [$level] [$time]";
|
||||
|
||||
@@ -138,7 +136,7 @@ class LogConfig {
|
||||
|
||||
class SuperLogging {
|
||||
/// The logger for SuperLogging
|
||||
static final $ = log.Logger('ente_logging');
|
||||
static final $ = Logger('ente_logging');
|
||||
|
||||
/// The current super logging configuration
|
||||
static late LogConfig config;
|
||||
@@ -170,8 +168,8 @@ class SuperLogging {
|
||||
setupSentry().ignore();
|
||||
}
|
||||
|
||||
log.Logger.root.level = log.Level.ALL;
|
||||
log.Logger.root.onRecord.listen(onLogRecord);
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen(onLogRecord);
|
||||
|
||||
if (!enable) {
|
||||
$.info("detected debug mode; sentry & file logging disabled.");
|
||||
@@ -226,7 +224,7 @@ class SuperLogging {
|
||||
|
||||
static String _lastExtraLines = '';
|
||||
|
||||
static Future onLogRecord(log.LogRecord rec) async {
|
||||
static Future onLogRecord(LogRecord rec) async {
|
||||
// log misc info if it changed
|
||||
String? extraLines = "app version: '$appVersion'\n";
|
||||
if (extraLines != _lastExtraLines) {
|
||||
@@ -1,13 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/endpoint_updated_event.dart';
|
||||
import 'package:ente_auth/utils/package_info_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:ua_client_hints/ua_client_hints.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
int kConnectTimeout = 15000;
|
||||
@@ -16,19 +17,20 @@ class Network {
|
||||
late Dio _dio;
|
||||
late Dio _enteDio;
|
||||
|
||||
Future<void> init(BaseConfiguration configuration) async {
|
||||
final String ua = await userAgent();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
final version = packageInfo.version;
|
||||
final packageName = packageInfo.packageName;
|
||||
final endpoint = configuration.getHttpEndpoint();
|
||||
final isMobile = Platform.isAndroid || Platform.isIOS;
|
||||
Future<void> init() async {
|
||||
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: isMobile ? ua : Platform.operatingSystem,
|
||||
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||
? FkUserAgent.userAgent
|
||||
: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
@@ -40,8 +42,8 @@ class Network {
|
||||
baseUrl: endpoint,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
if (isMobile)
|
||||
HttpHeaders.userAgentHeader: ua
|
||||
if (PlatformUtil.isMobile())
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||
else
|
||||
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
@@ -53,12 +55,12 @@ class Network {
|
||||
_dio.httpClientAdapter = NativeAdapter();
|
||||
_enteDio.httpClientAdapter = NativeAdapter();
|
||||
|
||||
_setupInterceptors(configuration);
|
||||
_setupInterceptors(endpoint);
|
||||
|
||||
Bus.instance.on<EndpointUpdatedEvent>().listen((event) {
|
||||
final endpoint = configuration.getHttpEndpoint();
|
||||
final endpoint = Configuration.instance.getHttpEndpoint();
|
||||
_enteDio.options.baseUrl = endpoint;
|
||||
_setupInterceptors(configuration);
|
||||
_setupInterceptors(endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -69,12 +71,12 @@ class Network {
|
||||
Dio getDio() => _dio;
|
||||
Dio get enteDio => _enteDio;
|
||||
|
||||
void _setupInterceptors(BaseConfiguration configuration) {
|
||||
void _setupInterceptors(String endpoint) {
|
||||
_dio.interceptors.clear();
|
||||
_dio.interceptors.add(RequestIdInterceptor());
|
||||
|
||||
_enteDio.interceptors.clear();
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(configuration));
|
||||
_enteDio.interceptors.add(EnteRequestInterceptor(endpoint));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,21 +90,21 @@ class RequestIdInterceptor extends Interceptor {
|
||||
}
|
||||
|
||||
class EnteRequestInterceptor extends Interceptor {
|
||||
final BaseConfiguration configuration;
|
||||
final String endpoint;
|
||||
|
||||
EnteRequestInterceptor(this.configuration);
|
||||
EnteRequestInterceptor(this.endpoint);
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
options.baseUrl == configuration.getHttpEndpoint(),
|
||||
options.baseUrl == endpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
);
|
||||
}
|
||||
options.headers
|
||||
.putIfAbsent("x-request-id", () => const Uuid().v4().toString());
|
||||
final String? tokenValue = configuration.getToken();
|
||||
final String? tokenValue = Configuration.instance.getToken();
|
||||
if (tokenValue != null) {
|
||||
options.headers.putIfAbsent("X-Auth-Token", () => tokenValue);
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_events/models/event.dart';
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class CodesUpdatedEvent extends Event {}
|
||||
|
||||
3
mobile/apps/auth/lib/events/endpoint_updated_event.dart
Normal file
3
mobile/apps/auth/lib/events/endpoint_updated_event.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class EndpointUpdatedEvent extends Event {}
|
||||
@@ -1 +1,3 @@
|
||||
|
||||
|
||||
class Event {}
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_events/models/event.dart';
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class IconsChangedEvent extends Event {}
|
||||
|
||||
5
mobile/apps/auth/lib/events/notification_event.dart
Normal file
5
mobile/apps/auth/lib/events/notification_event.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
// NotificationEvent event is used to re-fresh the UI to show latest notification
|
||||
// (if any)
|
||||
class NotificationEvent extends Event {}
|
||||
3
mobile/apps/auth/lib/events/signed_in_event.dart
Normal file
3
mobile/apps/auth/lib/events/signed_in_event.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class SignedInEvent extends Event {}
|
||||
@@ -1 +1,3 @@
|
||||
// TODO Implement this library.
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class SignedOutEvent extends Event {}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_events/models/event.dart';
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class TriggerLogoutEvent extends Event {}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import 'package:ente_events/models/event.dart';
|
||||
import 'package:ente_auth/events/event.dart';
|
||||
|
||||
class UserDetailsChangedEvent extends Event {}
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_key.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
|
||||
class AuthenticatorGateway {
|
||||
late Dio _enteDio;
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Použijte možnost \"Export the vault\" v nastavení aplikace Aegis.",
|
||||
"import2FasGuide": "Použijte možnost \"Settings->Backup -Export\" v 2FA.\n\nPokud je Vaše záloha šifrovaná, budete muset zadat heslo pro její odemčení",
|
||||
"importLastpassGuide": "V nastavení aplikace Lastpass Authenticator vyberte možnost \"Transfer accounts\" a poté \"Export accounts to file\". Vygenerovaný soubor JSON následně nahrajte sem.",
|
||||
"importProtonAuthGuide": "K exportu kódů použijte možnost „Exportovat“ v nastavení aplikace Proton Authenticator.",
|
||||
"exportCodes": "Exportovat kódy",
|
||||
"importLabel": "Importovat",
|
||||
"importInstruction": "Vyberte, prosím, soubor obsahující seznam Vašich kódů v následujícím formátu",
|
||||
@@ -125,7 +124,6 @@
|
||||
"authToChangeYourEmail": "Pro změnu svého e-mailu se, prosím, ověřte",
|
||||
"authToChangeYourPassword": "Pro změnu svého hesla se, prosím, ověřte",
|
||||
"authToViewSecrets": "Pro zobrazení svých tajných údajů se musíte ověřit",
|
||||
"authToInitiateSignIn": "Proveďte ověření a přihlaste se k zálohování.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Zrušit",
|
||||
"yes": "Ano",
|
||||
@@ -173,7 +171,6 @@
|
||||
"invalidQRCode": "Neplatný QR kód",
|
||||
"noRecoveryKeyTitle": "Nemáte obnovovací klíč?",
|
||||
"enterEmailHint": "Zadejte svou e-mailovou adresu",
|
||||
"enterNewEmailHint": "Zadejte svou novou e-mailovou adresu",
|
||||
"invalidEmailTitle": "Neplatná e-mailová adresa",
|
||||
"invalidEmailMessage": "Prosím, zadejte platnou e-mailovou adresu.",
|
||||
"deleteAccount": "Odstranit účet",
|
||||
@@ -512,19 +509,6 @@
|
||||
"supportEnte": "Podpořte <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Dejte nám hvězdu na Githubu",
|
||||
"free5GB": "5GB zdarma na <bold-green>ente</bold-green> Fotky",
|
||||
"loginWithAuthAccount": "Přihlaste se pomocí svého účtu Auth",
|
||||
"freeStorageOffer": "10% sleva na <bold-green>ente</bold-green> fotky",
|
||||
"freeStorageOfferDescription": "Použijte kód \"AUTH\" pro získání 10% slevy na první rok",
|
||||
"advanced": "Pokročilé",
|
||||
"algorithm": "Algoritmus",
|
||||
"type": "Typ",
|
||||
"period": "Období",
|
||||
"digits": "Digitální",
|
||||
"importFromGallery": "Importovat z galerie",
|
||||
"errorCouldNotReadImage": "Nelze přečíst vybraný obrazový soubor.",
|
||||
"errorInvalidQRCode": "Neplatný QR kód",
|
||||
"errorInvalidQRCodeBody": "Naskenovaný QR kód není platným účtem 2FA.",
|
||||
"errorNoQRCode": "Nenalezen žádný QR kód",
|
||||
"errorGenericTitle": "Došlo k chybě",
|
||||
"errorGenericBody": "Při importu došlo k neočekávané chybě."
|
||||
"freeStorageOfferDescription": "Použijte kód \"AUTH\" pro získání 10% slevy na první rok"
|
||||
}
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Verwenden Sie die Option \"Tresor exportieren\" in den Einstellungen von Aegis.\n\nFalls Ihr Tresor verschlüsselt ist, müssen Sie das Passwort für den Tresor eingeben, um ihn zu entschlüsseln.",
|
||||
"import2FasGuide": "Verwenden Sie unter \"Einstellungen → Backup\" die Option \"Exportieren\" in 2FAS.\n\nFalls Ihr Backup verschlüsselt ist, müssen Sie das Passwort eingeben, um das Backup zu entschlüsseln.",
|
||||
"importLastpassGuide": "Verwenden Sie die Option \"Konten übertragen → Konten in Datei exportieren\" in den Lastpass Authenticator Einstellungen. \nImportieren Sie anschließend die heruntergeladene JSON-Datei.",
|
||||
"importProtonAuthGuide": "Verwenden Sie die Option \"Exportieren\" in den Proton Authenticator Settings um Ihre Codes zu exportieren.",
|
||||
"exportCodes": "Codes exportieren",
|
||||
"importLabel": "Importieren",
|
||||
"importInstruction": "Bitte wählen Sie eine Datei die Codes in folgendem Format beinhaltet",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "Algorithmus",
|
||||
"type": "Typ",
|
||||
"period": "Periode",
|
||||
"digits": "Ziffern",
|
||||
"importFromGallery": "Aus Galerie importieren",
|
||||
"errorCouldNotReadImage": "Die ausgewählte Bild-Datei konnte nicht verarbeitet werden.",
|
||||
"errorInvalidQRCode": "Ungültiger QR-Code",
|
||||
"errorInvalidQRCodeBody": "Der gescannte QR-Code ist kein gültiges 2FA-Konto.",
|
||||
"errorNoQRCode": "Kein QR-Code gefunden",
|
||||
"errorGenericTitle": "Ein Fehler ist aufgetreten",
|
||||
"errorGenericBody": "Beim Importieren ist ein unerwarteter Fehler aufgetreten."
|
||||
"digits": "Ziffern"
|
||||
}
|
||||
@@ -88,8 +88,6 @@
|
||||
"useRecoveryKey": "Χρήση κλειδιού ανάκτησης",
|
||||
"incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης",
|
||||
"welcomeBack": "Καλωσορίσατε και πάλι!",
|
||||
"emailAlreadyRegistered": "Το email είναι ήδη καταχωρημένο.",
|
||||
"emailNotRegistered": "Το email δεν έχει καταχωρηθεί.",
|
||||
"madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ",
|
||||
"supportDevs": "Εγγραφείτε στο <bold-green>ente</bold-green> για να μας υποστηρίξετε",
|
||||
"supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
@@ -173,7 +171,6 @@
|
||||
"invalidQRCode": "Μη έγκυρος κωδικός QR",
|
||||
"noRecoveryKeyTitle": "Χωρίς κλειδί ανάκτησης;",
|
||||
"enterEmailHint": "Εισάγετε τη διεύθυνση email σας",
|
||||
"enterNewEmailHint": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας",
|
||||
"invalidEmailTitle": "Μη έγκυρη διεύθυνση email",
|
||||
"invalidEmailMessage": "Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.",
|
||||
"deleteAccount": "Διαγραφή λογαριασμού",
|
||||
@@ -261,10 +258,6 @@
|
||||
"areYouSureYouWantToLogout": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
|
||||
"yesLogout": "Ναι, αποσύνδεση",
|
||||
"exit": "Εξοδος",
|
||||
"theme": "Θέμα",
|
||||
"lightTheme": "Φωτεινό",
|
||||
"darkTheme": "Σκοτεινό",
|
||||
"systemTheme": "Σύστημα",
|
||||
"verifyingRecoveryKey": "Επαλήθευση κλειδιού ανάκτησης...",
|
||||
"recoveryKeyVerified": "Το κλειδί ανάκτησης επαληθεύτηκε",
|
||||
"recoveryKeySuccessBody": "Τέλεια! Το κλειδί ανάκτησης σας είναι έγκυρο. Σας ευχαριστούμε για την επαλήθευση.\n\nΠαρακαλώ θυμηθείτε να κρατήσετε το κλειδί ανάκτησης σας και σε αντίγραφο ασφαλείας.",
|
||||
@@ -497,24 +490,5 @@
|
||||
"appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο",
|
||||
"appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής",
|
||||
"authToViewPasskey": "Παρακαλώ πιστοποιηθείτε για να δείτε το κλειδί πρόσβασης",
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας.",
|
||||
"duplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"noDuplicates": "✨ Δεν υπάρχουν διπλότυπα",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Δεν υπάρχουν διπλότυπα αρχεία που μπορούν να εκκαθαριστούν",
|
||||
"deduplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"deselectAll": "Αποεπιλογή όλων",
|
||||
"selectAll": "Επιλογή όλων",
|
||||
"deleteDuplicates": "Διαγραφή διπλότυπων",
|
||||
"plainHTML": "Απλό HTML",
|
||||
"dropReviewiOS": "Αφήστε μια κριτική στο App Store",
|
||||
"dropReviewAndroid": "Αφήστε μια κριτική στο Play Store",
|
||||
"giveUsAStarOnGithub": "Δώστε μας ένα αστέρι στο Github",
|
||||
"free5GB": "5GB δωρεάν στο <bold-green>ente</bold-green> Photos",
|
||||
"freeStorageOffer": "10% έκπτωση στο <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Χρησιμοποιήστε τον κωδικό \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
"advanced": "Για προχωρημένους",
|
||||
"algorithm": "Αλγόριθμος",
|
||||
"type": "Τύπος",
|
||||
"period": "Περίοδος",
|
||||
"digits": "Ψηφία"
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας."
|
||||
}
|
||||
@@ -111,7 +111,6 @@
|
||||
"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.",
|
||||
"importProtonAuthGuide": "Use the \"Export\" option in Proton Authenticator Settings to export your codes.",
|
||||
"exportCodes": "Export codes",
|
||||
"importLabel": "Import",
|
||||
"importInstruction": "Please select a file that contains a list of your codes in the following format",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "Algorithm",
|
||||
"type": "Type",
|
||||
"period": "Period",
|
||||
"digits": "Digits",
|
||||
"importFromGallery": "Import from gallery",
|
||||
"errorCouldNotReadImage": "Could not read the selected image file.",
|
||||
"errorInvalidQRCode": "Invalid QR Code",
|
||||
"errorInvalidQRCodeBody": "The scanned QR code is not a valid 2FA account.",
|
||||
"errorNoQRCode": "No QR code found",
|
||||
"errorGenericTitle": "An Error Occurred",
|
||||
"errorGenericBody": "An unexpected error occurred while importing."
|
||||
"digits": "Digits"
|
||||
}
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Utilisez l'option \"Exporter le coffre-fort\" dans les paramètres d'Aegis.\n\nSi votre coffre-fort est crypté, vous devrez saisir le mot de passe du coffre-fort pour déchiffrer le coffre-fort.",
|
||||
"import2FasGuide": "Utilisez l'option \"Paramètres->Sauvegarde -Export\" dans 2FAS.\n\nSi votre sauvegarde est chiffrée, vous devrez entrer le mot de passe pour déchiffrer la sauvegarde",
|
||||
"importLastpassGuide": "Utilisez l'option \"Transférer des comptes\" dans les paramètres de l'authentificateur Lastpass et appuyez sur \"Exporter des comptes vers un fichier\". Importez le JSON téléchargé.",
|
||||
"importProtonAuthGuide": "Utilisez l'option \"Export\" dans les paramètres de Proton Authenticator pour exporter vos codes.",
|
||||
"exportCodes": "Exporter les codes",
|
||||
"importLabel": "Importer",
|
||||
"importInstruction": "Veuillez sélectionner un fichier qui contient une liste de vos codes dans le format suivant",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "Algorithme",
|
||||
"type": "Type",
|
||||
"period": "Période",
|
||||
"digits": "Chiffres",
|
||||
"importFromGallery": "Importer depuis la galerie",
|
||||
"errorCouldNotReadImage": "Impossible de lire le fichier sélectionné.",
|
||||
"errorInvalidQRCode": "QR Code invalide",
|
||||
"errorInvalidQRCodeBody": "Le code QR scanné n'est pas un compte 2FA valide.",
|
||||
"errorNoQRCode": "Aucun code QR trouvé",
|
||||
"errorGenericTitle": "Une erreur s'est produite",
|
||||
"errorGenericBody": "Une erreur inattendue est survenue lors de l'importation."
|
||||
"digits": "Chiffres"
|
||||
}
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Naudokite „Aegis“ nustatymuose esančią parinktį Eksportuoti slėptuvę.\n\nJei jūsų saugykla užšifruota, turėsite įvesti saugyklos slaptažodį, kad iššifruotumėte saugyklą.",
|
||||
"import2FasGuide": "Naudokite programoje 2FAS esančią parinktį „Settings->2FAS Backup->Export to file“.\n\nJei atsarginė kopija užšifruota, turėsite įvesti slaptažodį, kad iššifruotumėte atsarginę kopiją.",
|
||||
"importLastpassGuide": "Naudokite „Lastpass Authenticator“ nustatymuose esančią parinktį „Transfer accounts“ (perkelti paskyras) ir paspauskite „Export accounts to file“ (eksportuoti paskyras į failą). Importuokite atsisiųstą JSON failą.",
|
||||
"importProtonAuthGuide": "Naudokite „Proton Authenticator“ nustatymuose esančią parinktį „Export“ (eksportuoti), kad eksportuotumėte savo kodus.",
|
||||
"exportCodes": "Eksportuoti kodus",
|
||||
"importLabel": "Importuoti",
|
||||
"importInstruction": "Pasirinkite failą, kuriame yra tokio formato jūsų kodų sąrašas",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "Algoritmas",
|
||||
"type": "Tipas",
|
||||
"period": "Laikotarpis",
|
||||
"digits": "Skaitmenys",
|
||||
"importFromGallery": "Importuoti iš galerijos",
|
||||
"errorCouldNotReadImage": "Nepavyko perskaityti pasirinkto vaizdo failo.",
|
||||
"errorInvalidQRCode": "Netinkamas QR kodas",
|
||||
"errorInvalidQRCodeBody": "Nuskenuotas QR kodas nėra tinkama 2FA paskyra.",
|
||||
"errorNoQRCode": "QR kodas nerastas.",
|
||||
"errorGenericTitle": "Įvyko klaida",
|
||||
"errorGenericBody": "Importuojant įvyko netikėta klaida."
|
||||
"digits": "Skaitmenys"
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
"timeBasedKeyType": "Oparte na czasie (TOTP)",
|
||||
"counterBasedKeyType": "Oparte na liczniku (HOTP)",
|
||||
"saveAction": "Zapisz",
|
||||
"nextTotpTitle": "dalej",
|
||||
"nextTotpTitle": "następny",
|
||||
"deleteCodeTitle": "Usunąć kod?",
|
||||
"deleteCodeMessage": "Czy na pewno chcesz usunąć ten kod? Ta akcja jest nieodwracalna.",
|
||||
"trashCode": "Przenieść kod do kosza?",
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Użyj opcji \"Eksportuj sejf\" w ustawieniach Aegis.\n\nJeśli twój sejf jest zaszyfrowany, musisz wprowadzić hasło sejfu, aby odszyfrować sejf.",
|
||||
"import2FasGuide": "Użyj opcji \"Ustawienia->Kopia Zapasowa-Eksport\" w 2FAS.\n\nJeśli twoja kopia zapasowa jest zaszyfrowana, musisz wprowadzić hasło, aby odszyfrować kopię zapasową",
|
||||
"importLastpassGuide": "Użyj opcji \"Przenieś konta\" w Ustawieniach Lastpass Authenticator i naciśnij \"Eksportuj konta do pliku\". Zaimportuj pobrany plik JSON.",
|
||||
"importProtonAuthGuide": "Użyj opcji „Eksportuj” w ustawieniach Proton Authenticator, aby wyeksportować kody.",
|
||||
"exportCodes": "Eksportuj kody",
|
||||
"importLabel": "Importuj",
|
||||
"importInstruction": "Wybierz plik, który zawiera listę twoich kodów w następującym formacie",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "Algorytm",
|
||||
"type": "Rodzaj",
|
||||
"period": "Okres",
|
||||
"digits": "Cyfry",
|
||||
"importFromGallery": "Importuj z galerii",
|
||||
"errorCouldNotReadImage": "Nie można odczytać wybranego pliku obrazu.",
|
||||
"errorInvalidQRCode": "Nieprawidłowy kod QR",
|
||||
"errorInvalidQRCodeBody": "Zeskanowany kod QR nie wskazuje na prawidłowe konto 2FA.",
|
||||
"errorNoQRCode": "Nie znaleziono kodu QR",
|
||||
"errorGenericTitle": "Wystąpił błąd",
|
||||
"errorGenericBody": "Podczas importowania wystąpił nieoczekiwany błąd."
|
||||
"digits": "Cyfry"
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
"incorrectDetails": "Felaktiga uppgifter",
|
||||
"pleaseVerifyDetails": "Kontrollera dina detaljer och försök igen",
|
||||
"codeIssuerHint": "Utfärdare",
|
||||
"codeSecretKeyHint": "Hemlig nyckel",
|
||||
"codeSecretKeyHint": "Secret Key",
|
||||
"secret": "Säkerhetsnyckel",
|
||||
"all": "Alla",
|
||||
"notes": "Anteckningar",
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Konto (du@domain.com)",
|
||||
"codeAccountHint": "Konto (du@domän.com)",
|
||||
"codeTagHint": "Tagg",
|
||||
"accountKeyType": "Typ av nyckel",
|
||||
"sessionExpired": "Sessionen har gått ut",
|
||||
@@ -68,7 +68,7 @@
|
||||
"reportABug": "Rapportera en bugg",
|
||||
"crashAndErrorReporting": "Krasch och felrapportering",
|
||||
"reportBug": "Rapportera bugg",
|
||||
"emailUsMessage": "Skicka e-post till {email}",
|
||||
"emailUsMessage": "Skicka e-mail till {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
@@ -79,7 +79,7 @@
|
||||
"contactSupport": "Kontakta support",
|
||||
"rateUsOnStore": "Betygsätt på {storeName}",
|
||||
"blog": "Blogg",
|
||||
"merchandise": "Produkter",
|
||||
"merchandise": "Merchandise",
|
||||
"verifyPassword": "Bekräfta lösenord",
|
||||
"pleaseWait": "Vänligen vänta...",
|
||||
"generatingEncryptionKeysTitle": "Skapar krypteringsnycklar...",
|
||||
@@ -104,14 +104,13 @@
|
||||
"importFromApp": "Importera koder från {appName}",
|
||||
"importGoogleAuthGuide": "Exportera dina konton från Google Authenticator till en QR-kod med alternativet \"Överföra konton\". Använd sedan en annan enhet och skanna QR-koden.\n\nTips: Du kan använda din bärbara dators webbkamera för att ta en bild av QR-koden.",
|
||||
"importSelectJsonFile": "Välj JSON-fil",
|
||||
"importSelectAppExport": "Välj {appName} exporteringsfil",
|
||||
"importSelectAppExport": "Välj {appName} exportfil",
|
||||
"importEnteEncGuide": "Välj den krypterade JSON-filen som exporteras från Ente",
|
||||
"importRaivoGuide": "Använd alternativet \"Exportera OTPs till zip-arkiv\" i Raivos inställningar.\n\nExtrahera zip-filen och importera JSON-filen.",
|
||||
"importBitwardenGuide": "Använd alternativet \"Exportera valv\" inom Bitwarden Tools och importera den okrypterade JSON-filen.",
|
||||
"importAegisGuide": "Använd alternativet \"Exportera valvet\" i Aegis inställningar.\n\nOm ditt valv är krypterat måste du ange valvlösenordet för att dekryptera valvet.",
|
||||
"import2FasGuide": "Använd alternativet \"Inställningar->Säkerhetskopiera -Exportera\" i 2FAS.\n\nOm din säkerhetskopia är krypterad måste du ange lösenordet för att dekryptera säkerhetskopian.",
|
||||
"importLastpassGuide": "Använd alternativet \"Överför konton\" i LastPass Authenticators inställningar och tryck på \"Exportera konton till fil\". Importera JSON-filen som laddas ner.",
|
||||
"importProtonAuthGuide": "Använd alternativet \"Exportera\" i Proton Authenticator-inställningarna för att exportera koder.",
|
||||
"exportCodes": "Exportera koder",
|
||||
"importLabel": "Importera",
|
||||
"importInstruction": "Vänligen välj en fil som innehåller en lista över dina koder i följande format",
|
||||
@@ -120,11 +119,11 @@
|
||||
"emailVerificationToggle": "E-postverifiering",
|
||||
"emailVerificationEnableWarning": "För att undvika att bli låst från ditt konto, se till att spara en kopia av din e-post 2FA utanför Ente Auth innan du aktiverar e-postverifiering.",
|
||||
"authToChangeEmailVerificationSetting": "Autentisera för att ändra din e-postadress",
|
||||
"authenticateGeneric": "Vänligen autentisera",
|
||||
"authenticateGeneric": "Var god autentisera",
|
||||
"authToViewYourRecoveryKey": "Autentisera för att visa din återställningsnyckel",
|
||||
"authToChangeYourEmail": "Autentisera för att ändra din e-postadress",
|
||||
"authToChangeYourPassword": "Autentisera för att ändra ditt lösenord",
|
||||
"authToViewSecrets": "Vänligen autentisera för att visa din återställningsnyckel",
|
||||
"authToViewSecrets": "Autentisera för att visa din återställningsnyckel",
|
||||
"authToInitiateSignIn": "Vänligen autentisera för att initiera inloggning för säkerhetskopiering.",
|
||||
"ok": "OK",
|
||||
"cancel": "Avbryt",
|
||||
@@ -148,7 +147,7 @@
|
||||
"leaveFamily": "Lämna familjen",
|
||||
"leaveFamilyMessage": "Är du säker på att du vill lämna familjeplanen?",
|
||||
"inFamilyPlanMessage": "Du är på en familjeplan!",
|
||||
"hintForMobile": "Tryck länge på en kod för att redigera eller ta bort.",
|
||||
"hintForMobile": "Håll i på en kod för att redigera eller ta bort.",
|
||||
"hintForDesktop": "Högerklicka på en kod för att redigera eller ta bort.",
|
||||
"scan": "Skanna",
|
||||
"scanACode": "Skanna kod",
|
||||
@@ -192,7 +191,7 @@
|
||||
"oopsSomethingWentWrong": "Hoppsan! Något gick fel.",
|
||||
"selectLanguage": "Välj språk",
|
||||
"language": "Språk",
|
||||
"social": "Socialt",
|
||||
"social": "Social",
|
||||
"security": "Säkerhet",
|
||||
"lockscreen": "Låsskärm",
|
||||
"authToChangeLockscreenSetting": "Vänligen autentisera för att ändra låsskärms inställningar",
|
||||
@@ -201,7 +200,7 @@
|
||||
"authToViewYourActiveSessions": "Autentisera för att visa dina aktiva sessioner",
|
||||
"searchHint": "Sök...",
|
||||
"search": "Sök",
|
||||
"sorryUnableToGenCode": "Tyvärr, kunde inte generera en kod för {issuerName}",
|
||||
"sorryUnableToGenCode": "Tyvärr, det gick inte att generera en kod för {issuerName}",
|
||||
"noResult": "Inga resultat",
|
||||
"addCode": "Lägg till kod",
|
||||
"scanAQrCode": "Skanna en QR-kod",
|
||||
@@ -216,7 +215,7 @@
|
||||
"error": "Fel",
|
||||
"recoveryKeyCopiedToClipboard": "Återställningsnyckel kopierad till urklipp",
|
||||
"recoveryKeyOnForgotPassword": "Om du glömmer ditt lösenord är det enda sättet du kan återställa dina data med denna nyckel.",
|
||||
"recoveryKeySaveDescription": "Vi lagrar inte och har därför inte åtkomst till denna nyckel, vänligen spara denna 24 ordsnyckeln på en säker plats.",
|
||||
"recoveryKeySaveDescription": "Vi lagrar inte och har därför inte åtkomst till denna nyckel, vänligen spara denna 24 ords nyckel på en säker plats.",
|
||||
"doThisLater": "Gör detta senare",
|
||||
"saveKey": "Spara nyckel",
|
||||
"save": "Spara",
|
||||
@@ -255,7 +254,7 @@
|
||||
"insecureDevice": "Osäker enhet",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Tyvärr, kunde vi inte generera säkra nycklar på den här enheten.\n\nvänligen registrera dig från en annan enhet.",
|
||||
"howItWorks": "Så här fungerar det",
|
||||
"ackPasswordLostWarning": "Jag förstår att om jag förlorar mitt lösenord kan jag förlora mina data eftersom min data är <underline>totalsträckskrypterad</underline>.",
|
||||
"ackPasswordLostWarning": "Jag förstår att om jag förlorar mitt lösenord kan jag förlora mina data eftersom min data är <underline>end-to-end-krypterad</underline>.",
|
||||
"loginTerms": "Jag samtycker till <u-terms>användarvillkoren</u-terms> och <u-policy>integritetspolicyn</u-policy>",
|
||||
"logInLabel": "Logga in",
|
||||
"logout": "Logga ut",
|
||||
@@ -279,7 +278,7 @@
|
||||
"recoveryKeyVerifyReason": "Din återställningsnyckel är det enda sättet att återställa dina foton om du glömmer ditt lösenord. Du hittar din återställningsnyckel i Inställningar > Säkerhet.\n\nAnge din återställningsnyckel här för att verifiera att du har sparat den ordentligt.",
|
||||
"confirmYourRecoveryKey": "Bekräfta din återställningsnyckel",
|
||||
"confirm": "Bekräfta",
|
||||
"emailYourLogs": "E-posta dina loggar",
|
||||
"emailYourLogs": "Maila dina loggar",
|
||||
"pleaseSendTheLogsTo": "Vänligen skicka loggarna till \n{toEmail}",
|
||||
"copyEmailAddress": "Kopiera e-postadress",
|
||||
"exportLogs": "Exportera loggar",
|
||||
@@ -298,7 +297,7 @@
|
||||
"criticalUpdateAvailable": "Kritisk uppdatering tillgänglig",
|
||||
"updateAvailable": "Uppdatering tillgänglig",
|
||||
"update": "Uppdatera",
|
||||
"checking": "Kontrollerar...",
|
||||
"checking": "Kontrollerar ...",
|
||||
"youAreOnTheLatestVersion": "Du är på den senaste versionen",
|
||||
"warning": "Varning",
|
||||
"exportWarningDesc": "Den exporterade filen innehåller känslig information. Förvara den på ett säkert sätt.",
|
||||
@@ -307,7 +306,7 @@
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "Autentisera för att exportera dina koder",
|
||||
"importSuccessTitle": "Hurra!",
|
||||
"importSuccessTitle": "Jippi!",
|
||||
"importSuccessDesc": "Du har importerat {count} koder!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
@@ -325,7 +324,7 @@
|
||||
"checkInboxAndSpamFolder": "Vänligen kontrollera din inkorg (och skräppost) för att slutföra verifieringen",
|
||||
"tapToEnterCode": "Tryck för att ange kod",
|
||||
"resendEmail": "Skicka e-post igen",
|
||||
"weHaveSendEmailTo": "Vi har skickat ett e-postmeddelande till <green>{email}</green>",
|
||||
"weHaveSendEmailTo": "Vi har skickat ett mail till <green>{email}</green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
@@ -363,7 +362,7 @@
|
||||
"selectExportFormat": "Välj exportformat",
|
||||
"exportDialogDesc": "Krypterad export skyddas av ett lösenord som du väljer.",
|
||||
"encrypted": "Krypterad",
|
||||
"plainText": "Oformaterad text",
|
||||
"plainText": "Enkel text",
|
||||
"passwordToEncryptExport": "Lösenord för att kryptera export",
|
||||
"export": "Exportera",
|
||||
"useOffline": "Använd utan säkerhetskopior",
|
||||
@@ -375,14 +374,14 @@
|
||||
"compactMode": "Kompakt läge",
|
||||
"shouldHideCode": "Dölj koder",
|
||||
"doubleTapToViewHiddenCode": "Du kan dubbeltrycka på en post för att visa koden",
|
||||
"focusOnSearchBar": "Fokusera på sök vid uppstart av app",
|
||||
"focusOnSearchBar": "Fokusera på sök vid appstart",
|
||||
"confirmUpdatingkey": "Är du säker på att du vill uppdatera den hemliga nyckeln?",
|
||||
"minimizeAppOnCopy": "Minimera appen vid kopiering",
|
||||
"editCodeAuthMessage": "Autentisera för att redigera kod",
|
||||
"deleteCodeAuthMessage": "Autentisera för att radera kod",
|
||||
"showQRAuthMessage": "Autentisera för att visa QR-kod",
|
||||
"confirmAccountDeleteTitle": "Bekräfta radering av kontot",
|
||||
"confirmAccountDeleteMessage": "Detta konto är kopplat till andra Ente applikationer, om du använder någon.\n\nDina uppladdade data, över alla Ente applikationer, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.",
|
||||
"confirmAccountDeleteMessage": "Detta konto är kopplat till andra Ente apps, om du använder någon.\n\nDina uppladdade data, över alla Ente appar, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.",
|
||||
"androidBiometricHint": "Verifiera identitet",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
@@ -391,7 +390,7 @@
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Lyckades",
|
||||
"androidBiometricSuccess": "Slutförd",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
@@ -442,7 +441,7 @@
|
||||
"signOutOtherDevices": "Logga ut andra enheter",
|
||||
"doNotSignOut": "Logga inte ut",
|
||||
"hearUsWhereTitle": "Hur hörde du talas om Ente? (valfritt)",
|
||||
"hearUsExplanation": "Vi spårar inte installationer. Det skulle hjälpa oss om du berättade hur du hittade oss!",
|
||||
"hearUsExplanation": "Vi spårar inte appinstallationer, Det skulle hjälpa oss om du berättade var du hittade oss!",
|
||||
"recoveryKeySaved": "Återställningsnyckel sparad i nedladdningsmappen!",
|
||||
"waitingForBrowserRequest": "Väntar på webbläsarbegäran...",
|
||||
"waitingForVerification": "Väntar på verifiering...",
|
||||
@@ -489,8 +488,6 @@
|
||||
"hideContent": "Dölj innehåll",
|
||||
"hideContentDescriptionAndroid": "Döljer appinnehåll i app-växlaren och inaktiverar skärmdumpar",
|
||||
"hideContentDescriptioniOS": "Döljer appinnehåll i app-växlaren",
|
||||
"autoLockFeatureDescription": "Tid efter vilken appen låses efter att ha satts i bakgrunden",
|
||||
"appLockDescription": "Välj mellan enhetens förvalda låsskärm och en anpassad låsskärm med en PIN-kod eller lösenord.",
|
||||
"pinLock": "Pinkodslås",
|
||||
"enterPin": "Ange PIN-kod",
|
||||
"setNewPin": "Ställ in ny PIN-kod",
|
||||
@@ -501,31 +498,9 @@
|
||||
"appLockOfflineModeWarning": "Du har valt att fortsätta utan säkerhetskopior. Om du glömmer ditt applås, kommer du att bli utelåst från att komma åt dina data.",
|
||||
"duplicateCodes": "Dubblettkoder",
|
||||
"noDuplicates": "✨ Inga dubbletter",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Du har inga dubbla koder som kan rensas",
|
||||
"deduplicateCodes": "Deduplicera koder",
|
||||
"deselectAll": "Avmarkera alla",
|
||||
"selectAll": "Markera alla",
|
||||
"deleteDuplicates": "Radera dubbletter",
|
||||
"plainHTML": "Ren HTML",
|
||||
"tellUsWhatYouThink": "Berätta vad du tycker",
|
||||
"dropReviewiOS": "Skriv en recension på App Store",
|
||||
"dropReviewAndroid": "Skriv en recension på Play Store",
|
||||
"supportEnte": "Stöd <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Ge oss en stjärna på Github",
|
||||
"free5GB": "5 GB gratis på <bold-green>ente</bold-green> Foton",
|
||||
"loginWithAuthAccount": "Logga in med ditt Auth-konto",
|
||||
"freeStorageOffer": "10% rabatt på <bold-green>ente</bold-green> foton",
|
||||
"freeStorageOfferDescription": "Använd koden \"AUTH\" för att få 10% rabatt första året",
|
||||
"advanced": "Avancerad",
|
||||
"algorithm": "Algoritm",
|
||||
"type": "Typ",
|
||||
"period": "Tidsperiod",
|
||||
"digits": "Siffror",
|
||||
"importFromGallery": "Importera från galleri",
|
||||
"errorCouldNotReadImage": "Kunde inte läsa den valda bildfilen.",
|
||||
"errorInvalidQRCode": "Ogiltig QR-kod",
|
||||
"errorInvalidQRCodeBody": "Den skannade QR-koden är inte ett giltigt 2FA konto.",
|
||||
"errorNoQRCode": "Ingen QR-kod hittades",
|
||||
"errorGenericTitle": "Ett fel inträffade",
|
||||
"errorGenericBody": "Ett oväntat fel inträffade vid import."
|
||||
"plainHTML": "Ren HTML"
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "妥善保管您的双重认证代码",
|
||||
"onBoardingBody": "妥善保管您的两步验证码",
|
||||
"onBoardingGetStarted": "开始",
|
||||
"setupFirstAccount": "设置您的第一个账户",
|
||||
"importScanQrCode": "扫描二维码",
|
||||
@@ -111,14 +111,13 @@
|
||||
"importAegisGuide": "使用 Aegis 设置中的“导出密码库”选项。\n\n如果您的密码库已加密,则需要输入密码库密码才能解密密码库。",
|
||||
"import2FasGuide": "使用 2FAS 中的“设置 -> 备份 -> 导出”选项。\n\n如果您的备份已加密,则需要输入密码来解密备份",
|
||||
"importLastpassGuide": "使用 Lastpass Authenticator 设置中的“转移账户”选项,然后按“将账户导出到文件”。导入下载的 JSON。",
|
||||
"importProtonAuthGuide": "使用 Proton Authenticator 设置中的“导出”选项导出您的代码。",
|
||||
"exportCodes": "导出代码",
|
||||
"importLabel": "导入",
|
||||
"importInstruction": "请选择一个包含以下格式的代码列表的文件",
|
||||
"importCodeDelimiterInfo": "代码可以用逗号或换行符分隔",
|
||||
"selectFile": "选择文件",
|
||||
"emailVerificationToggle": "电子邮件验证",
|
||||
"emailVerificationEnableWarning": "为避免被锁在您的账户之外,请在启用电子邮件验证之前确保在 Ente Auth 之外存储电子邮件双重认证的副本。",
|
||||
"emailVerificationEnableWarning": "为避免被锁在您的账户之外,请在启用电子邮件验证之前确保在 Ente Auth 之外存储电子邮件两步验证的副本。",
|
||||
"authToChangeEmailVerificationSetting": "请进行身份验证以更改电子邮件验证",
|
||||
"authenticateGeneric": "请验证",
|
||||
"authToViewYourRecoveryKey": "请验证以查看您的恢复密钥",
|
||||
@@ -156,7 +155,7 @@
|
||||
"verifyEmail": "验证电子邮件",
|
||||
"enterCodeHint": "从你的身份验证器应用中\n输入6位数字代码",
|
||||
"lostDeviceTitle": "丢失了设备吗?",
|
||||
"twoFactorAuthTitle": "双重认证",
|
||||
"twoFactorAuthTitle": "两步验证",
|
||||
"passkeyAuthTitle": "通行密钥验证",
|
||||
"verifyPasskey": "验证通行密钥",
|
||||
"loginWithTOTP": "使用 TOTP 登录",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "算法",
|
||||
"type": "类型",
|
||||
"period": "周期",
|
||||
"digits": "数字",
|
||||
"importFromGallery": "从图库导入",
|
||||
"errorCouldNotReadImage": "无法读取所选图片文件。",
|
||||
"errorInvalidQRCode": "二维码无效",
|
||||
"errorInvalidQRCodeBody": "扫描的二维码不是有效的双重认证账户。",
|
||||
"errorNoQRCode": "未找到二维码",
|
||||
"errorGenericTitle": "出错了",
|
||||
"errorGenericBody": "导入时发生意外错误。"
|
||||
"digits": "数字"
|
||||
}
|
||||
@@ -6,4 +6,4 @@ export "package:ente_auth/l10n/arb/app_localizations.dart"
|
||||
|
||||
extension AppLocalizationsX on BuildContext {
|
||||
AppLocalizations get l10n => AppLocalizations.of(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,38 +2,34 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:adaptive_theme/adaptive_theme.dart';
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import "package:ente_auth/app/view/app.dart";
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/logging/super_logging.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/services/billing_service.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/services/update_service.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/services/window_listener_service.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/code_display_store.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/ui/home_page.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/directory_utils.dart';
|
||||
import 'package:ente_auth/utils/lock_screen_settings.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:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen.dart';
|
||||
import 'package:ente_logging/logging.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:ente_strings/l10n/strings_localizations.dart';
|
||||
import 'package:ente_ui/theme/theme_config.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:tray_manager/tray_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
@@ -93,13 +89,11 @@ void main() async {
|
||||
}
|
||||
|
||||
Future<void> _runInForeground() async {
|
||||
AppThemeConfig.initialize(EnteApp.auth);
|
||||
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
|
||||
final configuration = Configuration.instance;
|
||||
return await _runWithLogs(() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
try {
|
||||
await init(false, via: 'mainMethod');
|
||||
await _init(false, via: 'mainMethod');
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to init", e, s);
|
||||
rethrow;
|
||||
@@ -109,19 +103,12 @@ Future<void> _runInForeground() async {
|
||||
runApp(
|
||||
AppLock(
|
||||
builder: (args) => App(locale: locale),
|
||||
lockScreen: LockScreen(configuration),
|
||||
lockScreen: const LockScreen(),
|
||||
enabled: await LockScreenSettings.instance.shouldShowLockScreen(),
|
||||
locale: locale,
|
||||
lightTheme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
savedThemeMode: savedThemeMode,
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
localizationsDelegates: const [
|
||||
...StringsLocalizations.localizationsDelegates,
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
],
|
||||
supportedLocales: appSupportedLocales,
|
||||
backgroundLockLatency: const Duration(seconds: 0),
|
||||
),
|
||||
);
|
||||
});
|
||||
@@ -160,20 +147,20 @@ void _registerWindowsProtocol() {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> init(bool bool, {String? via}) async {
|
||||
Future<void> _init(bool bool, {String? via}) async {
|
||||
_registerWindowsProtocol();
|
||||
await CryptoUtil.init();
|
||||
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await CodeDisplayStore.instance.init();
|
||||
await Configuration.instance.init([AuthenticatorDB.instance]);
|
||||
await Network.instance.init(Configuration.instance);
|
||||
await UserService.instance.init(Configuration.instance, const HomePage());
|
||||
await Configuration.instance.init();
|
||||
await Network.instance.init();
|
||||
await UserService.instance.init();
|
||||
await AuthenticatorService.instance.init();
|
||||
await BillingService.instance.init();
|
||||
await NotificationService.instance.init();
|
||||
await UpdateService.instance.init();
|
||||
await IconUtils.instance.init();
|
||||
await LockScreenSettings.instance.init(Configuration.instance);
|
||||
await LockScreenSettings.instance.init();
|
||||
}
|
||||
|
||||
@@ -81,7 +81,6 @@ class CompleteSRPSetupRequest {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SrpAttributes {
|
||||
final String srpUserID;
|
||||
final String srpSalt;
|
||||
@@ -1,7 +1,7 @@
|
||||
import "dart:typed_data";
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ente_base/models/key_attributes.dart';
|
||||
import 'package:ente_base/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
|
||||
class KeyGenResult {
|
||||
final KeyAttributes keyAttributes;
|
||||
65
mobile/apps/auth/lib/models/subscription.dart
Normal file
65
mobile/apps/auth/lib/models/subscription.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
const freeProductID = "free";
|
||||
const stripe = "stripe";
|
||||
const appStore = "appstore";
|
||||
const playStore = "playstore";
|
||||
|
||||
class Subscription {
|
||||
final String productID;
|
||||
final int storage;
|
||||
final String originalTransactionID;
|
||||
final String paymentProvider;
|
||||
final int expiryTime;
|
||||
final String price;
|
||||
final String period;
|
||||
final Attributes? attributes;
|
||||
|
||||
Subscription({
|
||||
required this.productID,
|
||||
required this.storage,
|
||||
required this.originalTransactionID,
|
||||
required this.paymentProvider,
|
||||
required this.expiryTime,
|
||||
required this.price,
|
||||
required this.period,
|
||||
this.attributes,
|
||||
});
|
||||
|
||||
bool isValid() {
|
||||
return expiryTime > DateTime.now().microsecondsSinceEpoch;
|
||||
}
|
||||
|
||||
bool isYearlyPlan() {
|
||||
return 'year' == period;
|
||||
}
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
return Subscription(
|
||||
productID: map['productID'],
|
||||
storage: map['storage'],
|
||||
originalTransactionID: map['originalTransactionID'],
|
||||
paymentProvider: map['paymentProvider'],
|
||||
expiryTime: map['expiryTime'],
|
||||
price: map['price'],
|
||||
period: map['period'],
|
||||
attributes: map["attributes"] != null
|
||||
? Attributes.fromJson(map["attributes"])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Attributes {
|
||||
bool? isCancelled;
|
||||
String? customerID;
|
||||
|
||||
Attributes({
|
||||
this.isCancelled,
|
||||
this.customerID,
|
||||
});
|
||||
|
||||
Attributes.fromJson(dynamic json) {
|
||||
isCancelled = json["isCancelled"];
|
||||
customerID = json["customerID"];
|
||||
}
|
||||
}
|
||||
146
mobile/apps/auth/lib/models/user_details.dart
Normal file
146
mobile/apps/auth/lib/models/user_details.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_auth/models/subscription.dart';
|
||||
|
||||
class UserDetails {
|
||||
final String email;
|
||||
final int usage;
|
||||
final int fileCount;
|
||||
final int sharedCollectionsCount;
|
||||
final Subscription subscription;
|
||||
final FamilyData? familyData;
|
||||
final ProfileData? profileData;
|
||||
|
||||
UserDetails(
|
||||
this.email,
|
||||
this.usage,
|
||||
this.fileCount,
|
||||
this.sharedCollectionsCount,
|
||||
this.subscription,
|
||||
this.familyData,
|
||||
this.profileData,
|
||||
);
|
||||
|
||||
bool isPartOfFamily() {
|
||||
return familyData?.members?.isNotEmpty ?? false;
|
||||
}
|
||||
|
||||
bool isFamilyAdmin() {
|
||||
assert(isPartOfFamily(), "verify user is part of family before calling");
|
||||
final FamilyMember currentUserMember = familyData!.members!
|
||||
.firstWhere((element) => element.email.trim() == email.trim());
|
||||
return currentUserMember.isAdmin;
|
||||
}
|
||||
|
||||
// getFamilyOrPersonalUsage will return total usage for family if user
|
||||
// belong to family group. Otherwise, it will return storage consumed by
|
||||
// current user
|
||||
int getFamilyOrPersonalUsage() {
|
||||
return isPartOfFamily() ? familyData!.getTotalUsage() : usage;
|
||||
}
|
||||
|
||||
int getFreeStorage() {
|
||||
return max(
|
||||
isPartOfFamily()
|
||||
? (familyData!.storage - familyData!.getTotalUsage())
|
||||
: (subscription.storage - (usage)),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
int getTotalStorage() {
|
||||
return isPartOfFamily() ? familyData!.storage : subscription.storage;
|
||||
}
|
||||
|
||||
factory UserDetails.fromMap(Map<String, dynamic> map) {
|
||||
return UserDetails(
|
||||
map['email'] as String,
|
||||
map['usage'] as int,
|
||||
(map['fileCount'] ?? 0) as int,
|
||||
(map['sharedCollectionsCount'] ?? 0) as int,
|
||||
Subscription.fromMap(map['subscription']),
|
||||
FamilyData.fromMap(map['familyData']),
|
||||
ProfileData.fromJson(map['profileData']),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FamilyMember {
|
||||
final String email;
|
||||
final int usage;
|
||||
final String id;
|
||||
final bool isAdmin;
|
||||
|
||||
FamilyMember(this.email, this.usage, this.id, this.isAdmin);
|
||||
|
||||
factory FamilyMember.fromMap(Map<String, dynamic> map) {
|
||||
return FamilyMember(
|
||||
(map['email'] ?? '') as String,
|
||||
map['usage'] as int,
|
||||
map['id'] as String,
|
||||
map['isAdmin'] as bool,
|
||||
);
|
||||
}
|
||||
}
|
||||
class ProfileData {
|
||||
bool canDisableEmailMFA;
|
||||
bool isEmailMFAEnabled;
|
||||
bool isTwoFactorEnabled;
|
||||
|
||||
// Constructor with default values
|
||||
ProfileData({
|
||||
this.canDisableEmailMFA = false,
|
||||
this.isEmailMFAEnabled = false,
|
||||
this.isTwoFactorEnabled = false,
|
||||
});
|
||||
|
||||
// Factory method to create ProfileData instance from JSON
|
||||
factory ProfileData.fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) null;
|
||||
|
||||
return ProfileData(
|
||||
canDisableEmailMFA: json!['canDisableEmailMFA'] ?? false,
|
||||
isEmailMFAEnabled: json['isEmailMFAEnabled'] ?? false,
|
||||
isTwoFactorEnabled: json['isTwoFactorEnabled'] ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
// Method to convert ProfileData instance to JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'canDisableEmailMFA': canDisableEmailMFA,
|
||||
'isEmailMFAEnabled': isEmailMFAEnabled,
|
||||
'isTwoFactorEnabled': isTwoFactorEnabled,
|
||||
};
|
||||
}
|
||||
String toJsonString() => json.encode(toJson());
|
||||
}
|
||||
class FamilyData {
|
||||
final List<FamilyMember>? members;
|
||||
|
||||
// Storage available based on the family plan
|
||||
final int storage;
|
||||
final int expiryTime;
|
||||
|
||||
FamilyData(this.members, this.storage, this.expiryTime);
|
||||
|
||||
int getTotalUsage() {
|
||||
return members!.map((e) => e.usage).toList().sum;
|
||||
}
|
||||
|
||||
static fromMap(Map<String, dynamic>? map) {
|
||||
if (map == null) return null;
|
||||
assert(map['members'] != null && map['members'].length >= 0);
|
||||
final members = List<FamilyMember>.from(
|
||||
map['members'].map((x) => FamilyMember.fromMap(x)),
|
||||
);
|
||||
return FamilyData(
|
||||
members,
|
||||
map['storage'] as int,
|
||||
map['expiryTime'] as int,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,19 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_accounts/pages/email_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/login_page.dart';
|
||||
import 'package:ente_accounts/pages/password_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/password_reentry_page.dart';
|
||||
import 'package:ente_auth/app/view/app.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
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';
|
||||
@@ -23,7 +24,6 @@ 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:ente_events/event_bus.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
@@ -260,22 +260,17 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
void _navigateToSignUpPage() {
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() == null) {
|
||||
page = EmailEntryPage(Configuration.instance);
|
||||
page = const EmailEntryPage();
|
||||
} else {
|
||||
// No key
|
||||
if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// Never had a key
|
||||
page = PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.set,
|
||||
const HomePage(),
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
);
|
||||
} else if (Configuration.instance.getKey() == null) {
|
||||
// Yet to decrypt the key
|
||||
page = PasswordReentryPage(
|
||||
Configuration.instance,
|
||||
const HomePage(),
|
||||
);
|
||||
page = const PasswordReentryPage();
|
||||
} else {
|
||||
// All is well, user just has not subscribed
|
||||
page = const HomePage();
|
||||
@@ -293,22 +288,17 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
void _navigateToSignInPage() {
|
||||
Widget page;
|
||||
if (Configuration.instance.getEncryptedToken() == null) {
|
||||
page = LoginPage(Configuration.instance);
|
||||
page = const LoginPage();
|
||||
} else {
|
||||
// No key
|
||||
if (Configuration.instance.getKeyAttributes() == null) {
|
||||
// Never had a key
|
||||
page = PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.set,
|
||||
const HomePage(),
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
);
|
||||
} else if (Configuration.instance.getKey() == null) {
|
||||
// Yet to decrypt the key
|
||||
page = PasswordReentryPage(
|
||||
Configuration.instance,
|
||||
const HomePage(),
|
||||
);
|
||||
page = const PasswordReentryPage();
|
||||
} else {
|
||||
// All is well, user just has not subscribed
|
||||
// page = getSubscriptionPage(isOnBoarding: true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/models/all_icon_data.dart';
|
||||
@@ -22,7 +23,6 @@ import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@ import 'dart:math';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/events/signed_in_event.dart';
|
||||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import 'package:ente_auth/gateway/authenticator.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
@@ -15,8 +17,6 @@ import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/signed_in_event.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/models/billing_plan.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/models/billing_plan.dart';
|
||||
import 'package:ente_auth/models/subscription.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
const kWebPaymentRedirectUrl = "https://payments.ente.io/frameRedirect";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_lock_screen/auth_util.dart';
|
||||
import 'package:ente_lock_screen/lock_screen_settings.dart';
|
||||
import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen_password.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen_pin.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_password.dart';
|
||||
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_pin.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/lock_screen_settings.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:ente_accounts/services/user_service.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/icons_changed_event.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
enum CodeSortKey {
|
||||
|
||||
@@ -2,9 +2,9 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/services/notification_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import 'dart:async';
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_accounts/models/delete_account.dart';
|
||||
import 'package:ente_accounts/models/errors.dart';
|
||||
import 'package:ente_accounts/models/sessions.dart';
|
||||
import 'package:ente_accounts/models/set_keys_request.dart';
|
||||
import 'package:ente_accounts/models/set_recovery_key_request.dart';
|
||||
import 'package:ente_accounts/models/srp.dart';
|
||||
import 'package:ente_accounts/models/two_factor.dart';
|
||||
import 'package:ente_accounts/models/user_details.dart';
|
||||
import 'package:ente_accounts/pages/login_page.dart';
|
||||
import 'package:ente_accounts/pages/ott_verification_page.dart';
|
||||
import 'package:ente_accounts/pages/passkey_page.dart';
|
||||
import 'package:ente_accounts/pages/password_entry_page.dart';
|
||||
import 'package:ente_accounts/pages/password_reentry_page.dart';
|
||||
import 'package:ente_accounts/pages/recovery_page.dart';
|
||||
import 'package:ente_accounts/pages/two_factor_authentication_page.dart';
|
||||
import 'package:ente_accounts/pages/two_factor_recovery_page.dart';
|
||||
import 'package:ente_base/models/key_attributes.dart';
|
||||
import 'package:ente_base/models/key_gen_result.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_configuration/constants.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/core/network.dart';
|
||||
import 'package:ente_auth/events/user_details_changed_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/account/two_factor.dart';
|
||||
import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/models/delete_account.dart';
|
||||
import 'package:ente_auth/models/key_attributes.dart';
|
||||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/sessions.dart';
|
||||
import 'package:ente_auth/models/set_keys_request.dart';
|
||||
import 'package:ente_auth/models/set_recovery_key_request.dart';
|
||||
import 'package:ente_auth/models/user_details.dart';
|
||||
import 'package:ente_auth/ui/account/login_page.dart';
|
||||
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/common/progress_dialog.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/passkey_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
|
||||
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_events/models/user_details_changed_event.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/progress_dialog.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import "package:flutter/foundation.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -45,8 +44,6 @@ import "package:pointycastle/srp/srp6_verifier_generator.dart";
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import "package:uuid/uuid.dart";
|
||||
|
||||
const String kAccountsUrl = "https://accounts.ente.io";
|
||||
|
||||
class UserService {
|
||||
static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
|
||||
static const keyUserDetails = "user_details";
|
||||
@@ -57,19 +54,18 @@ class UserService {
|
||||
final _dio = Network.instance.getDio();
|
||||
final _enteDio = Network.instance.enteDio;
|
||||
final _logger = Logger((UserService).toString());
|
||||
final _config = Configuration.instance;
|
||||
late SharedPreferences _preferences;
|
||||
|
||||
late ValueNotifier<String?> emailValueNotifier;
|
||||
late BaseConfiguration _config;
|
||||
late BaseHomePage _homePage;
|
||||
|
||||
UserService._privateConstructor();
|
||||
|
||||
static final UserService instance = UserService._privateConstructor();
|
||||
|
||||
Future<void> init(BaseConfiguration config, BaseHomePage homePage) async {
|
||||
_config = config;
|
||||
_homePage = homePage;
|
||||
emailValueNotifier = ValueNotifier<String?>(config.getEmail());
|
||||
Future<void> init() async {
|
||||
emailValueNotifier =
|
||||
ValueNotifier<String?>(Configuration.instance.getEmail());
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
@@ -81,7 +77,7 @@ class UserService {
|
||||
bool isResetPasswordScreen = false,
|
||||
String? purpose,
|
||||
}) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
@@ -89,7 +85,6 @@ class UserService {
|
||||
data: {
|
||||
"email": email,
|
||||
"purpose": isChangeEmail ? "change" : purpose ?? "",
|
||||
"mobile": Platform.isIOS || Platform.isAndroid,
|
||||
},
|
||||
);
|
||||
await dialog.hide();
|
||||
@@ -119,24 +114,24 @@ class UserService {
|
||||
unawaited(
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.emailAlreadyRegistered,
|
||||
context.l10n.oops,
|
||||
context.l10n.emailAlreadyRegistered,
|
||||
),
|
||||
);
|
||||
} else if (enteErrCode != null && enteErrCode == "USER_NOT_REGISTERED") {
|
||||
unawaited(
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.emailNotRegistered,
|
||||
context.l10n.oops,
|
||||
context.l10n.emailNotRegistered,
|
||||
),
|
||||
);
|
||||
} else if (e.response != null && e.response!.statusCode == 403) {
|
||||
unawaited(
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.thisEmailIsAlreadyInUse,
|
||||
context.l10n.oops,
|
||||
context.l10n.thisEmailIsAlreadyInUse,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
@@ -199,13 +194,6 @@ class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
UserDetails? getCachedUserDetails() {
|
||||
if (_preferences.containsKey(keyUserDetails)) {
|
||||
return UserDetails.fromJson(_preferences.getString(keyUserDetails)!);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Sessions> getActiveSessions() async {
|
||||
try {
|
||||
final response = await _enteDio.get("/users/sessions");
|
||||
@@ -243,7 +231,7 @@ class UserService {
|
||||
try {
|
||||
final response = await _enteDio.post("/users/logout");
|
||||
if (response.statusCode == 200) {
|
||||
await _config.logout();
|
||||
await Configuration.instance.logout();
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
} else {
|
||||
throw Exception("Log out action failed");
|
||||
@@ -252,7 +240,7 @@ class UserService {
|
||||
_logger.severe(e);
|
||||
// check if token is already invalid
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
await _config.logout();
|
||||
await Configuration.instance.logout();
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
return;
|
||||
}
|
||||
@@ -302,7 +290,7 @@ class UserService {
|
||||
);
|
||||
if (response.statusCode == 200) {
|
||||
// clear data
|
||||
await _config.logout();
|
||||
await Configuration.instance.logout();
|
||||
} else {
|
||||
throw Exception("delete action failed");
|
||||
}
|
||||
@@ -339,7 +327,7 @@ class UserService {
|
||||
|
||||
Future<void> onPassKeyVerified(BuildContext context, Map response) async {
|
||||
final ProgressDialog dialog =
|
||||
createProgressDialog(context, context.strings.pleaseWait);
|
||||
createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final userPassword = _config.getVolatilePassword();
|
||||
@@ -350,10 +338,7 @@ class UserService {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return PasswordReentryPage(
|
||||
_config,
|
||||
_homePage,
|
||||
);
|
||||
return const PasswordReentryPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -366,7 +351,7 @@ class UserService {
|
||||
_config.getKeyAttributes()!,
|
||||
);
|
||||
_config.resetVolatilePassword();
|
||||
page = _homePage;
|
||||
page = const HomePage();
|
||||
} else {
|
||||
throw Exception("unexpected response during passkey verification");
|
||||
}
|
||||
@@ -394,7 +379,7 @@ class UserService {
|
||||
String ott, {
|
||||
bool isResettingPasswordScreen = false,
|
||||
}) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
final verifyData = {
|
||||
"email": _config.getEmail(),
|
||||
@@ -420,7 +405,6 @@ class UserService {
|
||||
}
|
||||
if (passkeySessionID.isNotEmpty) {
|
||||
page = PasskeyPage(
|
||||
_config,
|
||||
passkeySessionID,
|
||||
totp2FASessionID: twoFASessionID,
|
||||
accountsUrl: accountsUrl,
|
||||
@@ -429,23 +413,15 @@ class UserService {
|
||||
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||
} else {
|
||||
await _saveConfiguration(response);
|
||||
if (_config.getEncryptedToken() != null) {
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
if (isResettingPasswordScreen) {
|
||||
page = RecoveryPage(
|
||||
_config,
|
||||
_homePage,
|
||||
);
|
||||
page = const RecoveryPage();
|
||||
} else {
|
||||
page = PasswordReentryPage(
|
||||
_config,
|
||||
_homePage,
|
||||
);
|
||||
page = const PasswordReentryPage();
|
||||
}
|
||||
} else {
|
||||
page = PasswordEntryPage(
|
||||
_config,
|
||||
PasswordEntryMode.set,
|
||||
_homePage,
|
||||
page = const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.set,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -468,16 +444,16 @@ class UserService {
|
||||
if (e.response != null && e.response!.statusCode == 410) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.yourVerificationCodeHasExpired,
|
||||
context.l10n.oops,
|
||||
context.l10n.yourVerificationCodeHasExpired,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.incorrectCode,
|
||||
context.strings.sorryTheCodeYouveEnteredIsIncorrect,
|
||||
context.l10n.incorrectCode,
|
||||
context.l10n.sorryTheCodeYouveEnteredIsIncorrect,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -486,8 +462,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.verificationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -502,7 +478,7 @@ class UserService {
|
||||
String email,
|
||||
String ott,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _enteDio.post(
|
||||
@@ -514,7 +490,7 @@ class UserService {
|
||||
);
|
||||
await dialog.hide();
|
||||
if (response.statusCode == 200) {
|
||||
showShortToast(context, context.strings.emailChangedTo(email));
|
||||
showShortToast(context, context.l10n.emailChangedTo(email));
|
||||
await setEmail(email);
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
Bus.instance.fire(UserDetailsChangedEvent());
|
||||
@@ -523,8 +499,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.verificationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
await dialog.hide();
|
||||
@@ -532,15 +508,15 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.thisEmailIsAlreadyInUse,
|
||||
context.l10n.oops,
|
||||
context.l10n.thisEmailIsAlreadyInUse,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.incorrectCode,
|
||||
context.strings.authenticationFailedPleaseTryAgain,
|
||||
context.l10n.incorrectCode,
|
||||
context.l10n.authenticationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -549,8 +525,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.verificationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -742,10 +718,9 @@ class UserService {
|
||||
response.data["twoFactorSessionIDV2"] != null) {
|
||||
twoFASessionID = response.data["twoFactorSessionIDV2"];
|
||||
}
|
||||
_config.setVolatilePassword(userPassword);
|
||||
Configuration.instance.setVolatilePassword(userPassword);
|
||||
if (passkeySessionID.isNotEmpty) {
|
||||
page = PasskeyPage(
|
||||
_config,
|
||||
passkeySessionID,
|
||||
totp2FASessionID: twoFASessionID,
|
||||
accountsUrl: accountsUrl,
|
||||
@@ -754,13 +729,13 @@ class UserService {
|
||||
page = TwoFactorAuthenticationPage(twoFASessionID);
|
||||
} else {
|
||||
await _saveConfiguration(response);
|
||||
if (_config.getEncryptedToken() != null) {
|
||||
await _config.decryptSecretsAndGetKeyEncKey(
|
||||
if (Configuration.instance.getEncryptedToken() != null) {
|
||||
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
userPassword,
|
||||
_config.getKeyAttributes()!,
|
||||
Configuration.instance.getKeyAttributes()!,
|
||||
keyEncryptionKey: keyEncryptionKey,
|
||||
);
|
||||
page = _homePage;
|
||||
page = const HomePage();
|
||||
} else {
|
||||
throw Exception("unexpected response during email verification");
|
||||
}
|
||||
@@ -830,7 +805,7 @@ class UserService {
|
||||
String sessionID,
|
||||
String code,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
@@ -842,16 +817,13 @@ class UserService {
|
||||
);
|
||||
await dialog.hide();
|
||||
if (response.statusCode == 200) {
|
||||
showShortToast(context, context.strings.authenticationSuccessful);
|
||||
showShortToast(context, context.l10n.authenticationSuccessful);
|
||||
await _saveConfiguration(response);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return PasswordReentryPage(
|
||||
_config,
|
||||
_homePage,
|
||||
);
|
||||
return const PasswordReentryPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -866,7 +838,7 @@ class UserService {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPage(_config);
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -875,8 +847,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.incorrectCode,
|
||||
context.strings.authenticationFailedPleaseTryAgain,
|
||||
context.l10n.incorrectCode,
|
||||
context.l10n.authenticationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -885,8 +857,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.authenticationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.authenticationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -896,7 +868,7 @@ class UserService {
|
||||
String sessionID,
|
||||
TwoFactorType type,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
@@ -927,12 +899,12 @@ class UserService {
|
||||
await dialog.hide();
|
||||
_logger.severe(e);
|
||||
if (e.response != null && e.response!.statusCode == 404) {
|
||||
showToast(context, context.strings.sessionExpired);
|
||||
showToast(context, context.l10n.sessionExpired);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPage(_config);
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -941,8 +913,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.somethingWentWrongPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.somethingWentWrongPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -951,8 +923,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.somethingWentWrongPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.somethingWentWrongPleaseTryAgain,
|
||||
);
|
||||
} finally {
|
||||
await dialog.hide();
|
||||
@@ -967,7 +939,7 @@ class UserService {
|
||||
String encryptedSecret,
|
||||
String secretDecryptionNonce,
|
||||
) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
String secret;
|
||||
try {
|
||||
@@ -990,8 +962,8 @@ class UserService {
|
||||
await dialog.hide();
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.strings.incorrectRecoveryKey,
|
||||
context.strings.theRecoveryKeyYouEnteredIsIncorrect,
|
||||
context.l10n.incorrectRecoveryKey,
|
||||
context.l10n.theRecoveryKeyYouEnteredIsIncorrect,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1008,17 +980,14 @@ class UserService {
|
||||
if (response.statusCode == 200) {
|
||||
showShortToast(
|
||||
context,
|
||||
context.strings.twofactorAuthenticationSuccessfullyReset,
|
||||
context.l10n.twofactorAuthenticationSuccessfullyReset,
|
||||
);
|
||||
await _saveConfiguration(response);
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return PasswordReentryPage(
|
||||
_config,
|
||||
_homePage,
|
||||
);
|
||||
return const PasswordReentryPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -1033,7 +1002,7 @@ class UserService {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPage(_config);
|
||||
return const LoginPage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -1042,8 +1011,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.somethingWentWrongPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.somethingWentWrongPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -1052,8 +1021,8 @@ class UserService {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.somethingWentWrongPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.somethingWentWrongPleaseTryAgain,
|
||||
);
|
||||
} finally {
|
||||
await dialog.hide();
|
||||
@@ -1064,14 +1033,15 @@ class UserService {
|
||||
final responseData = response is Map ? response : response.data as Map?;
|
||||
if (responseData == null) return;
|
||||
|
||||
await _config.setUserID(responseData["id"]);
|
||||
await Configuration.instance.setUserID(responseData["id"]);
|
||||
if (responseData["encryptedToken"] != null) {
|
||||
await _config.setEncryptedToken(responseData["encryptedToken"]);
|
||||
await _config.setKeyAttributes(
|
||||
await Configuration.instance
|
||||
.setEncryptedToken(responseData["encryptedToken"]);
|
||||
await Configuration.instance.setKeyAttributes(
|
||||
KeyAttributes.fromMap(responseData["keyAttributes"]),
|
||||
);
|
||||
} else {
|
||||
await _config.setToken(responseData["token"]);
|
||||
await Configuration.instance.setToken(responseData["token"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@ 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:ente_base/ente_base.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 extends EnteBaseDatabase {
|
||||
class AuthenticatorDB {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
static const _databaseVersion = 1;
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/models/authenticator/entity_result.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/authenticator_service.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class CodeStore {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_utils/email_util.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
@@ -16,8 +16,9 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return AlertDialog(
|
||||
title: Text(context.strings.enterNewEmailHint),
|
||||
title: Text(l10n.enterNewEmailHint),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
@@ -25,7 +26,7 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
children: [
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
hintText: context.strings.email,
|
||||
hintText: l10n.email,
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.white30,
|
||||
),
|
||||
@@ -47,7 +48,7 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.cancel,
|
||||
l10n.cancel,
|
||||
style: const TextStyle(
|
||||
color: Colors.redAccent,
|
||||
),
|
||||
@@ -58,7 +59,7 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.verify,
|
||||
l10n.verify,
|
||||
style: const TextStyle(
|
||||
color: Colors.purple,
|
||||
),
|
||||
@@ -67,8 +68,8 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
if (!isValidEmail(_email)) {
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.invalidEmailTitle,
|
||||
context.strings.invalidEmailMessage,
|
||||
l10n.invalidEmailTitle,
|
||||
l10n.invalidEmailMessage,
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1,31 +1,29 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/delete_account.dart';
|
||||
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/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/gradient_button.dart';
|
||||
import 'package:ente_ui/components/dialogs.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:ente_utils/email_util.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DeleteAccountPage extends StatelessWidget {
|
||||
final BaseConfiguration config;
|
||||
|
||||
const DeleteAccountPage(
|
||||
this.config, {
|
||||
const DeleteAccountPage({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(context.strings.deleteAccount),
|
||||
title: Text(l10n.deleteAccount),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
@@ -49,7 +47,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
),
|
||||
Center(
|
||||
child: Text(
|
||||
context.strings.deleteAccountQuery,
|
||||
l10n.deleteAccountQuery,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
@@ -76,7 +74,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
height: 24,
|
||||
),
|
||||
GradientButton(
|
||||
text: context.strings.yesSendFeedbackAction,
|
||||
text: l10n.yesSendFeedbackAction,
|
||||
iconData: Icons.check,
|
||||
onTap: () async {
|
||||
await sendEmail(
|
||||
@@ -107,7 +105,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
backgroundColor: Colors.white,
|
||||
),
|
||||
label: Text(
|
||||
context.strings.noDeleteAccountAction,
|
||||
l10n.noDeleteAccountAction,
|
||||
style: const TextStyle(
|
||||
color: Colors.redAccent, // same for both themes
|
||||
),
|
||||
@@ -145,10 +143,11 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
BuildContext context,
|
||||
DeleteChallengeResponse response,
|
||||
) async {
|
||||
final l10n = context.l10n;
|
||||
final hasAuthenticated =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.strings.initiateAccountDeleteTitle,
|
||||
l10n.initiateAccountDeleteTitle,
|
||||
);
|
||||
|
||||
await PlatformUtil.refocusWindows();
|
||||
@@ -156,11 +155,11 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
if (hasAuthenticated) {
|
||||
final choice = await showChoiceDialogOld(
|
||||
context,
|
||||
context.strings.confirmAccountDeleteTitle,
|
||||
context.strings.confirmAccountDeleteMessage,
|
||||
firstAction: context.strings.cancel,
|
||||
secondAction: context.strings.delete,
|
||||
firstActionColor: getEnteColorScheme(context).surface,
|
||||
l10n.confirmAccountDeleteTitle,
|
||||
l10n.confirmAccountDeleteMessage,
|
||||
firstAction: l10n.cancel,
|
||||
secondAction: l10n.delete,
|
||||
firstActionColor: Theme.of(context).colorScheme.onSurface,
|
||||
secondActionColor: Colors.red,
|
||||
);
|
||||
if (choice != DialogUserChoice.secondChoice) {
|
||||
@@ -169,9 +168,9 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
final decryptChallenge = CryptoUtil.openSealSync(
|
||||
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||
CryptoUtil.base642bin(
|
||||
config.getKeyAttributes()!.publicKey,
|
||||
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||
),
|
||||
config.getSecretKey()!,
|
||||
Configuration.instance.getSecretKey()!,
|
||||
);
|
||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||
await UserService.instance.deleteAccount(context, challengeResponseStr);
|
||||
@@ -179,9 +178,10 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _requestEmailForDeletion(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: Text(
|
||||
context.strings.deleteAccount,
|
||||
l10n.deleteAccount,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
@@ -204,7 +204,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
style: TextStyle(
|
||||
color: getEnteColorScheme(context).surface,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
height: 1.5,
|
||||
fontSize: 16,
|
||||
),
|
||||
@@ -213,7 +213,7 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.sendEmail,
|
||||
l10n.sendEmail,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
@@ -229,9 +229,9 @@ class DeleteAccountPage extends StatelessWidget {
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.ok,
|
||||
l10n.ok,
|
||||
style: TextStyle(
|
||||
color: getEnteColorScheme(context).surface,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
@@ -1,20 +1,20 @@
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
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/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';
|
||||
import "package:step_progress_indicator/step_progress_indicator.dart";
|
||||
import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class EmailEntryPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
|
||||
const EmailEntryPage(this.config, {super.key});
|
||||
const EmailEntryPage({super.key});
|
||||
|
||||
@override
|
||||
State<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||
@@ -24,9 +24,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
static const kMildPasswordStrengthThreshold = 0.4;
|
||||
static const kStrongPasswordStrengthThreshold = 0.7;
|
||||
|
||||
final _config = Configuration.instance;
|
||||
final _passwordController1 = TextEditingController();
|
||||
final _passwordController2 = TextEditingController();
|
||||
Color? _validFieldValueColor;
|
||||
final Color _validFieldValueColor = const Color.fromARGB(51, 157, 45, 194);
|
||||
|
||||
String? _email;
|
||||
String? _password;
|
||||
@@ -48,7 +49,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = widget.config.getEmail();
|
||||
_email = _config.getEmail();
|
||||
_password1FocusNode.addListener(() {
|
||||
setState(() {
|
||||
_password1InFocus = _password1FocusNode.hasFocus;
|
||||
@@ -66,10 +67,6 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
|
||||
// Initialize theme-aware color
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
_validFieldValueColor = colorScheme.primary300.withOpacity(0.2);
|
||||
|
||||
FloatingActionButtonLocation? fabLocation() {
|
||||
if (isKeypadOpen) {
|
||||
return null;
|
||||
@@ -92,10 +89,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
child: StepProgressIndicator(
|
||||
totalSteps: 4,
|
||||
currentStep: 1,
|
||||
selectedColor: getEnteColorScheme(context).alternativeColor,
|
||||
selectedColor: Theme.of(context).colorScheme.alternativeColor,
|
||||
roundedEdges: const Radius.circular(10),
|
||||
unselectedColor:
|
||||
getEnteColorScheme(context).stepProgressUnselectedColor,
|
||||
Theme.of(context).colorScheme.stepProgressUnselectedColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -106,10 +103,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _isFormValid(),
|
||||
buttonText: context.strings.createAccount,
|
||||
buttonText: context.l10n.createAccount,
|
||||
onPressedFunction: () {
|
||||
UserService.instance.setEmail(_email!);
|
||||
widget.config.setVolatilePassword(_passwordController1.text);
|
||||
_config.setVolatilePassword(_passwordController1.text);
|
||||
UserService.instance.setRefSource(_referralSource);
|
||||
UserService.instance.sendOtt(
|
||||
context,
|
||||
@@ -126,13 +123,13 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
var passwordStrengthText = context.strings.weakStrength;
|
||||
var passwordStrengthText = context.l10n.weakStrength;
|
||||
var passwordStrengthColor = Colors.redAccent;
|
||||
if (_passwordStrength > kStrongPasswordStrengthThreshold) {
|
||||
passwordStrengthText = context.strings.strongStrength;
|
||||
passwordStrengthText = context.l10n.strongStrength;
|
||||
passwordStrengthColor = Colors.greenAccent;
|
||||
} else if (_passwordStrength > kMildPasswordStrengthThreshold) {
|
||||
passwordStrengthText = context.strings.moderateStrength;
|
||||
passwordStrengthText = context.l10n.moderateStrength;
|
||||
passwordStrengthColor = Colors.orangeAccent;
|
||||
}
|
||||
return Column(
|
||||
@@ -145,7 +142,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
context.strings.createNewAccount,
|
||||
context.l10n.createNewAccount,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -157,7 +154,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailIsValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.strings.email,
|
||||
hintText: context.l10n.email,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
@@ -170,7 +167,11 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
? Icon(
|
||||
Icons.check,
|
||||
size: 20,
|
||||
color: getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -202,7 +203,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
fillColor:
|
||||
_passwordIsValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.strings.password,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
@@ -225,7 +226,11 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
: _passwordIsValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
@@ -267,7 +272,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
? _validFieldValueColor
|
||||
: null,
|
||||
filled: true,
|
||||
hintText: context.strings.confirmPassword,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 14,
|
||||
@@ -290,7 +295,11 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color: getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
@@ -315,7 +324,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
child: Text(
|
||||
context.strings.passwordStrength(passwordStrengthText),
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -329,7 +338,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
|
||||
child: Text(
|
||||
context.strings.hearUsWhereTitle,
|
||||
context.l10n.hearUsWhereTitle,
|
||||
style: getEnteTextTheme(context).smallFaint,
|
||||
),
|
||||
),
|
||||
@@ -353,7 +362,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
onTap: () {
|
||||
showToast(
|
||||
context,
|
||||
context.strings.hearUsExplanation,
|
||||
context.l10n.hearUsExplanation,
|
||||
);
|
||||
},
|
||||
child: Icon(
|
||||
@@ -415,7 +424,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
Expanded(
|
||||
child: StyledText(
|
||||
text: context.strings.signUpTerms,
|
||||
text: context.l10n.signUpTerms,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -425,7 +434,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.termsOfServicesTitle,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -436,7 +445,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.privacyPolicyTitle,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -472,7 +481,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
),
|
||||
Expanded(
|
||||
child: StyledText(
|
||||
text: context.strings.ackPasswordLostWarning,
|
||||
text: context.l10n.ackPasswordLostWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -482,7 +491,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.encryption,
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -504,6 +513,4 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
||||
_hasAgreedToE2E &&
|
||||
_passwordIsValid;
|
||||
}
|
||||
|
||||
void showToast(BuildContext context, String hearUsExplanation) {}
|
||||
}
|
||||
@@ -1,26 +1,25 @@
|
||||
import "package:email_validator/email_validator.dart";
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_accounts/models/errors.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import "package:ente_strings/ente_strings.dart";
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
import 'package:email_validator/email_validator.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/models/api/user/srp.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:styled_text/tags/styled_text_tag_action.dart";
|
||||
import "package:styled_text/widgets/styled_text.dart";
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
|
||||
const LoginPage(this.config, {super.key});
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage> {
|
||||
final _config = Configuration.instance;
|
||||
bool _emailIsValid = false;
|
||||
String? _email;
|
||||
Color? _emailInputFieldColor;
|
||||
@@ -28,7 +27,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
Future<void> onPressed() async {
|
||||
await UserService.instance.setEmail(_email!);
|
||||
widget.config.resetVolatilePassword();
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
@@ -44,8 +43,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
widget.config,
|
||||
attr!,
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -63,7 +61,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = widget.config.getEmail();
|
||||
_email = _config.getEmail();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _emailIsValid,
|
||||
buttonText: context.strings.logInLabel,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
@@ -104,6 +102,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
final l10n = context.l10n;
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -114,7 +113,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
context.strings.welcomeBack,
|
||||
l10n.welcomeBack,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -127,7 +126,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailInputFieldColor,
|
||||
filled: true,
|
||||
hintText: context.strings.email,
|
||||
hintText: l10n.email,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 15,
|
||||
@@ -140,7 +139,11 @@ class _LoginPageState extends State<LoginPage> {
|
||||
? Icon(
|
||||
Icons.check,
|
||||
size: 20,
|
||||
color: getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -149,9 +152,8 @@ class _LoginPageState extends State<LoginPage> {
|
||||
_email = value.trim();
|
||||
_emailIsValid = EmailValidator.validate(_email!);
|
||||
if (_emailIsValid) {
|
||||
_emailInputFieldColor = getEnteColorScheme(context)
|
||||
.primary300
|
||||
.withOpacity(0.2);
|
||||
_emailInputFieldColor =
|
||||
const Color.fromARGB(51, 157, 45, 194);
|
||||
} else {
|
||||
_emailInputFieldColor = null;
|
||||
}
|
||||
@@ -176,14 +178,17 @@ class _LoginPageState extends State<LoginPage> {
|
||||
Expanded(
|
||||
flex: 5,
|
||||
child: StyledText(
|
||||
text: context.strings.loginTerms,
|
||||
style: getEnteTextTheme(context).small,
|
||||
text: context.l10n.loginTerms,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 12),
|
||||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.termsOfServicesTitle,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -194,7 +199,7 @@ class _LoginPageState extends State<LoginPage> {
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.privacyPolicyTitle,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
@@ -1,13 +1,14 @@
|
||||
import "package:dio/dio.dart";
|
||||
import "package:ente_accounts/ente_accounts.dart";
|
||||
import "package:ente_configuration/base_configuration.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:ente_strings/ente_strings.dart";
|
||||
import "package:ente_ui/components/buttons/button_widget.dart";
|
||||
import "package:ente_ui/components/buttons/dynamic_fab.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_ui/utils/dialog_util.dart";
|
||||
import "package:ente_utils/email_util.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
@@ -17,13 +18,8 @@ import "package:logging/logging.dart";
|
||||
// In the PasswordReentryPage, the password is auto-filled based on the
|
||||
// volatile password.
|
||||
class LoginPasswordVerificationPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final SrpAttributes srpAttributes;
|
||||
const LoginPasswordVerificationPage(
|
||||
this.config,
|
||||
this.srpAttributes, {
|
||||
super.key,
|
||||
});
|
||||
const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
|
||||
|
||||
@override
|
||||
State<LoginPasswordVerificationPage> createState() =>
|
||||
@@ -47,7 +43,7 @@ class _LoginPasswordVerificationPageState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
email = widget.config.getEmail();
|
||||
email = Configuration.instance.getEmail();
|
||||
_passwordFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_passwordInFocus = _passwordFocusNode.hasFocus;
|
||||
@@ -84,7 +80,7 @@ class _LoginPasswordVerificationPageState
|
||||
key: const ValueKey("verifyPasswordButton"),
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.strings.logInLabel,
|
||||
buttonText: context.l10n.logInLabel,
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
@@ -95,7 +91,7 @@ class _LoginPasswordVerificationPageState
|
||||
Future<void> verifyPassword(BuildContext context, String password) async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.strings.pleaseWait,
|
||||
context.l10n.pleaseWait,
|
||||
isDismissible: true,
|
||||
);
|
||||
await dialog.show();
|
||||
@@ -112,22 +108,22 @@ class _LoginPasswordVerificationPageState
|
||||
_logger.severe('server reject, failed verify SRP login', e, s);
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.strings.incorrectPasswordTitle,
|
||||
context.strings.pleaseTryAgain,
|
||||
context.l10n.incorrectPasswordTitle,
|
||||
context.l10n.pleaseTryAgain,
|
||||
);
|
||||
} else {
|
||||
_logger.severe('API failure during SRP login', e, s);
|
||||
if (e.type == DioExceptionType.connectionError) {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.strings.noInternetConnection,
|
||||
context.strings.pleaseCheckYourInternetConnectionAndTryAgain,
|
||||
context.l10n.noInternetConnection,
|
||||
context.l10n.pleaseCheckYourInternetConnectionAndTryAgain,
|
||||
);
|
||||
} else {
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.verificationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -147,9 +143,9 @@ class _LoginPasswordVerificationPageState
|
||||
// device is not powerful enough to perform derive key
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: context.strings.recreatePasswordTitle,
|
||||
body: context.strings.recreatePasswordBody,
|
||||
firstButtonLabel: context.strings.useRecoveryKey,
|
||||
title: context.l10n.recreatePasswordTitle,
|
||||
body: context.l10n.recreatePasswordBody,
|
||||
firstButtonLabel: context.l10n.useRecoveryKey,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
await UserService.instance.sendOtt(
|
||||
@@ -163,8 +159,8 @@ class _LoginPasswordVerificationPageState
|
||||
_logger.severe('unexpected error while verifying password', e, s);
|
||||
await _showContactSupportDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.verificationFailedPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.verificationFailedPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -179,13 +175,13 @@ class _LoginPasswordVerificationPageState
|
||||
context,
|
||||
title: title,
|
||||
body: message,
|
||||
firstButtonLabel: context.strings.contactSupport,
|
||||
secondButtonLabel: context.strings.ok,
|
||||
firstButtonLabel: context.l10n.contactSupport,
|
||||
secondButtonLabel: context.l10n.ok,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
await sendLogs(
|
||||
context,
|
||||
context.strings.contactSupport,
|
||||
context.l10n.contactSupport,
|
||||
postShare: () {},
|
||||
);
|
||||
}
|
||||
@@ -201,7 +197,7 @@ class _LoginPasswordVerificationPageState
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30, left: 20, right: 20),
|
||||
child: Text(
|
||||
context.strings.enterPassword,
|
||||
context.l10n.enterPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -239,7 +235,7 @@ class _LoginPasswordVerificationPageState
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
hintText: context.strings.enterYourPasswordHint,
|
||||
hintText: context.l10n.enterYourPasswordHint,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
@@ -299,7 +295,7 @@ class _LoginPasswordVerificationPageState
|
||||
},
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.strings.forgotPassword,
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -315,17 +311,17 @@ class _LoginPasswordVerificationPageState
|
||||
onTap: () async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.strings.pleaseWait,
|
||||
context.l10n.pleaseWait,
|
||||
);
|
||||
await dialog.show();
|
||||
await widget.config.logout();
|
||||
await Configuration.instance.logout();
|
||||
await dialog.hide();
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
},
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.strings.changeEmail,
|
||||
context.l10n.changeEmail,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.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/ui/common/dynamic_fab.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||
import 'package:styled_text/styled_text.dart';
|
||||
@@ -46,6 +46,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
|
||||
FloatingActionButtonLocation? fabLocation() {
|
||||
@@ -73,10 +74,10 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
child: StepProgressIndicator(
|
||||
totalSteps: 4,
|
||||
currentStep: 2,
|
||||
selectedColor: getEnteColorScheme(context).alternativeColor,
|
||||
selectedColor: Theme.of(context).colorScheme.alternativeColor,
|
||||
roundedEdges: const Radius.circular(10),
|
||||
unselectedColor:
|
||||
getEnteColorScheme(context).stepProgressUnselectedColor,
|
||||
Theme.of(context).colorScheme.stepProgressUnselectedColor,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
@@ -85,7 +86,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||
buttonText: context.strings.verify,
|
||||
buttonText: l10n.verify,
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
@@ -94,6 +95,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
}
|
||||
|
||||
Widget _getBody() {
|
||||
final l10n = context.l10n;
|
||||
return ListView(
|
||||
children: [
|
||||
Column(
|
||||
@@ -102,7 +104,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 30, 20, 15),
|
||||
child: Text(
|
||||
context.strings.verifyEmail,
|
||||
l10n.verifyEmail,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -117,8 +119,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 12),
|
||||
child: StyledText(
|
||||
text:
|
||||
context.strings.weHaveSendEmailTo(widget.email),
|
||||
text: l10n.weHaveSendEmailTo(widget.email),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -126,7 +127,8 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
tags: {
|
||||
'green': StyledTextTag(
|
||||
style: TextStyle(
|
||||
color: getEnteColorScheme(context)
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.alternativeColor,
|
||||
),
|
||||
),
|
||||
@@ -135,14 +137,14 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
),
|
||||
widget.isResetPasswordScreen
|
||||
? Text(
|
||||
context.strings.toResetVerifyEmail,
|
||||
l10n.toResetVerifyEmail,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontSize: 14),
|
||||
)
|
||||
: Text(
|
||||
context.strings.checkInboxAndSpamFolder,
|
||||
l10n.checkInboxAndSpamFolder,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -167,7 +169,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
hintText: context.strings.tapToEnterCode,
|
||||
hintText: l10n.tapToEnterCode,
|
||||
contentPadding: const EdgeInsets.all(15),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
@@ -202,7 +204,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
context.strings.resendEmail,
|
||||
l10n.resendEmail,
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
@@ -1,14 +1,15 @@
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/components/buttons/models/button_type.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import 'package:ente_utils/navigation_util.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
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/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';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -22,16 +23,9 @@ enum PasswordEntryMode {
|
||||
}
|
||||
|
||||
class PasswordEntryPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final PasswordEntryMode mode;
|
||||
final BaseHomePage homePage;
|
||||
|
||||
const PasswordEntryPage(
|
||||
this.config,
|
||||
this.mode,
|
||||
this.homePage, {
|
||||
super.key,
|
||||
});
|
||||
const PasswordEntryPage({required this.mode, super.key});
|
||||
|
||||
@override
|
||||
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
@@ -44,7 +38,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
final _logger = Logger((_PasswordEntryPageState).toString());
|
||||
final _passwordController1 = TextEditingController(),
|
||||
_passwordController2 = TextEditingController();
|
||||
Color? _validFieldValueColor;
|
||||
final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
|
||||
String? _volatilePassword;
|
||||
String _passwordInInputBox = '';
|
||||
String _passwordInInputConfirmationBox = '';
|
||||
@@ -62,7 +56,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_volatilePassword = widget.config.getVolatilePassword();
|
||||
_volatilePassword = Configuration.instance.getVolatilePassword();
|
||||
if (_volatilePassword != null) {
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
@@ -87,7 +81,6 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
_validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
|
||||
|
||||
FloatingActionButtonLocation? fabLocation() {
|
||||
if (isKeypadOpen) {
|
||||
@@ -97,13 +90,13 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
}
|
||||
|
||||
String title = context.strings.setPasswordTitle;
|
||||
String title = context.l10n.setPasswordTitle;
|
||||
if (widget.mode == PasswordEntryMode.update) {
|
||||
title = context.strings.changePasswordTitle;
|
||||
title = context.l10n.changePasswordTitle;
|
||||
} else if (widget.mode == PasswordEntryMode.reset) {
|
||||
title = context.strings.resetPasswordTitle;
|
||||
title = context.l10n.resetPasswordTitle;
|
||||
} else if (_volatilePassword != null) {
|
||||
title = context.strings.encryptionKeys;
|
||||
title = context.l10n.encryptionKeys;
|
||||
}
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: isKeypadOpen,
|
||||
@@ -139,14 +132,14 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
}
|
||||
|
||||
Widget _getBody(String buttonTextAndHeading) {
|
||||
final email = widget.config.getEmail();
|
||||
var passwordStrengthText = context.strings.weakStrength;
|
||||
final email = Configuration.instance.getEmail();
|
||||
var passwordStrengthText = context.l10n.weakStrength;
|
||||
var passwordStrengthColor = Colors.redAccent;
|
||||
if (_passwordStrength > kStrongPasswordStrengthThreshold) {
|
||||
passwordStrengthText = context.strings.strongStrength;
|
||||
passwordStrengthText = context.l10n.strongStrength;
|
||||
passwordStrengthColor = Colors.greenAccent;
|
||||
} else if (_passwordStrength > kMildPasswordStrengthThreshold) {
|
||||
passwordStrengthText = context.strings.moderateStrength;
|
||||
passwordStrengthText = context.l10n.moderateStrength;
|
||||
passwordStrengthColor = Colors.orangeAccent;
|
||||
}
|
||||
if (_volatilePassword != null) {
|
||||
@@ -174,8 +167,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Text(
|
||||
widget.mode == PasswordEntryMode.set
|
||||
? context.strings.enterPasswordToEncrypt
|
||||
: context.strings.enterNewPasswordToEncrypt,
|
||||
? context.l10n.enterPasswordToEncrypt
|
||||
: context.l10n.enterNewPasswordToEncrypt,
|
||||
textAlign: TextAlign.start,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
@@ -187,7 +180,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: StyledText(
|
||||
text: context.strings.passwordWarning,
|
||||
text: context.l10n.passwordWarning,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -229,11 +222,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
null);
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
fillColor: _isPasswordValid
|
||||
? _validFieldValueColor
|
||||
: getEnteColorScheme(context).fillFaint,
|
||||
fillColor:
|
||||
_isPasswordValid ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.strings.password,
|
||||
hintText: context.l10n.password,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
borderSide: BorderSide.none,
|
||||
@@ -257,8 +249,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
: _isPasswordValid
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color:
|
||||
getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -293,11 +288,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
onEditingComplete: () =>
|
||||
TextInput.finishAutofillContext(),
|
||||
decoration: InputDecoration(
|
||||
fillColor: _passwordsMatch
|
||||
? _validFieldValueColor
|
||||
: getEnteColorScheme(context).fillFaint,
|
||||
fillColor:
|
||||
_passwordsMatch ? _validFieldValueColor : null,
|
||||
filled: true,
|
||||
hintText: context.strings.confirmPassword,
|
||||
hintText: context.l10n.confirmPassword,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 20,
|
||||
@@ -320,8 +314,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
: _passwordsMatch
|
||||
? Icon(
|
||||
Icons.check,
|
||||
color:
|
||||
getEnteColorScheme(context).primary300,
|
||||
color: Theme.of(context)
|
||||
.inputDecorationTheme
|
||||
.focusedBorder!
|
||||
.borderSide
|
||||
.color,
|
||||
)
|
||||
: null,
|
||||
border: UnderlineInputBorder(
|
||||
@@ -351,7 +348,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
vertical: 8,
|
||||
),
|
||||
child: Text(
|
||||
context.strings.passwordStrength(passwordStrengthText),
|
||||
context.l10n.passwordStrength(passwordStrengthText),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
@@ -364,7 +361,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
onTap: () {
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.strings.howItWorks,
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
@@ -372,7 +369,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: context.strings.howItWorks,
|
||||
text: context.l10n.howItWorks,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
@@ -395,10 +392,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
void _updatePassword() async {
|
||||
final logOutFromOthers = await logOutFromOtherDevices(context);
|
||||
final dialog =
|
||||
createProgressDialog(context, context.strings.generatingEncryptionKeys);
|
||||
createProgressDialog(context, context.l10n.generatingEncryptionKeys);
|
||||
await dialog.show();
|
||||
try {
|
||||
final result = await widget.config
|
||||
final result = await Configuration.instance
|
||||
.getAttributesForNewPassword(_passwordController1.text);
|
||||
await UserService.instance.updateKeyAttributes(
|
||||
result.item1,
|
||||
@@ -406,7 +403,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
logoutOtherDevices: logOutFromOthers,
|
||||
);
|
||||
await dialog.hide();
|
||||
showShortToast(context, context.strings.passwordChangedSuccessfully);
|
||||
showShortToast(context, context.l10n.passwordChangedSuccessfully);
|
||||
Navigator.of(context).pop();
|
||||
if (widget.mode == PasswordEntryMode.reset) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
@@ -426,15 +423,15 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
bool logOutFromOther = true;
|
||||
await showChoiceDialog(
|
||||
context,
|
||||
title: context.strings.signOutFromOtherDevices,
|
||||
body: context.strings.signOutOtherBody,
|
||||
title: context.l10n.signOutFromOtherDevices,
|
||||
body: context.l10n.signOutOtherBody,
|
||||
isDismissible: false,
|
||||
firstButtonLabel: context.strings.signOutOtherDevices,
|
||||
firstButtonLabel: context.l10n.signOutOtherDevices,
|
||||
firstButtonType: ButtonType.critical,
|
||||
firstButtonOnTap: () async {
|
||||
logOutFromOther = true;
|
||||
},
|
||||
secondButtonLabel: context.strings.doNotSignOut,
|
||||
secondButtonLabel: context.l10n.doNotSignOut,
|
||||
secondButtonOnTap: () async {
|
||||
logOutFromOther = false;
|
||||
},
|
||||
@@ -446,31 +443,30 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
String password, {
|
||||
bool usingVolatilePassword = false,
|
||||
}) async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.strings.generatingEncryptionKeysTitle,
|
||||
);
|
||||
final l10n = context.l10n;
|
||||
final dialog =
|
||||
createProgressDialog(context, l10n.generatingEncryptionKeysTitle);
|
||||
await dialog.show();
|
||||
try {
|
||||
if (usingVolatilePassword) {
|
||||
_logger.info('Using volatile password');
|
||||
}
|
||||
final result = await widget.config.generateKey(password);
|
||||
widget.config.resetVolatilePassword();
|
||||
final KeyGenResult result =
|
||||
await Configuration.instance.generateKey(password);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
await dialog.hide();
|
||||
onDone() async {
|
||||
final dialog =
|
||||
createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
await UserService.instance.setAttributes(result);
|
||||
await dialog.hide();
|
||||
widget.config.resetVolatilePassword();
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return widget.homePage;
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
(route) => route.isFirst,
|
||||
@@ -490,9 +486,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
widget.config,
|
||||
result.privateKeyAttributes.recoveryKey,
|
||||
context.strings.continueLabel,
|
||||
context.l10n.continueLabel,
|
||||
showAppBar: false,
|
||||
isDismissible: false,
|
||||
onDone: onDone,
|
||||
@@ -506,8 +501,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.insecureDevice,
|
||||
context.strings.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
|
||||
context.l10n.insecureDevice,
|
||||
context.l10n.sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
|
||||
);
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
@@ -1,28 +1,22 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_accounts/models/errors.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/errors.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
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/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_utils/email_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PasswordReentryPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final BaseHomePage homePage;
|
||||
|
||||
const PasswordReentryPage(
|
||||
this.config,
|
||||
this.homePage, {
|
||||
super.key,
|
||||
});
|
||||
const PasswordReentryPage({super.key});
|
||||
|
||||
@override
|
||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||
@@ -40,8 +34,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
email = widget.config.getEmail();
|
||||
_volatilePassword = widget.config.getVolatilePassword();
|
||||
email = Configuration.instance.getEmail();
|
||||
_volatilePassword = Configuration.instance.getVolatilePassword();
|
||||
if (_volatilePassword != null) {
|
||||
_passwordController.text = _volatilePassword!;
|
||||
Future.delayed(
|
||||
@@ -85,7 +79,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
key: const ValueKey("verifyPasswordButton"),
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.strings.verifyPassword,
|
||||
buttonText: context.l10n.verifyPassword,
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
await verifyPassword(_passwordController.text);
|
||||
@@ -101,15 +95,15 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
bool usingVolatilePassword = false,
|
||||
}) async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
if (usingVolatilePassword) {
|
||||
_logger.info("Using volatile password");
|
||||
}
|
||||
try {
|
||||
final kek = await widget.config.decryptSecretsAndGetKeyEncKey(
|
||||
final kek = await Configuration.instance.decryptSecretsAndGetKeyEncKey(
|
||||
password,
|
||||
widget.config.getKeyAttributes()!,
|
||||
Configuration.instance.getKeyAttributes()!,
|
||||
);
|
||||
_registerSRPForExistingUsers(kek).ignore();
|
||||
} on KeyDerivationError catch (e, s) {
|
||||
@@ -117,19 +111,16 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
await dialog.hide();
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: context.strings.recreatePasswordTitle,
|
||||
body: context.strings.recreatePasswordBody,
|
||||
firstButtonLabel: context.strings.useRecoveryKey,
|
||||
title: context.l10n.recreatePasswordTitle,
|
||||
body: context.l10n.recreatePasswordBody,
|
||||
firstButtonLabel: context.l10n.useRecoveryKey,
|
||||
);
|
||||
if (dialogChoice!.action == ButtonAction.first) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return RecoveryPage(
|
||||
widget.config,
|
||||
widget.homePage,
|
||||
);
|
||||
return const RecoveryPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -140,27 +131,27 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
await dialog.hide();
|
||||
final dialogChoice = await showChoiceDialog(
|
||||
context,
|
||||
title: context.strings.incorrectPasswordTitle,
|
||||
body: context.strings.pleaseTryAgain,
|
||||
firstButtonLabel: context.strings.contactSupport,
|
||||
secondButtonLabel: context.strings.ok,
|
||||
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.strings.contactSupport,
|
||||
context.l10n.contactSupport,
|
||||
postShare: () {},
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
widget.config.resetVolatilePassword();
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
await dialog.hide();
|
||||
unawaited(
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return widget.homePage;
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
(route) => false,
|
||||
@@ -199,7 +190,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
context.strings.welcomeBack,
|
||||
context.l10n.welcomeBack,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -223,7 +214,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
hintText: context.strings.enterYourPasswordHint,
|
||||
hintText: context.l10n.enterYourPasswordHint,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
@@ -278,17 +269,14 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return RecoveryPage(
|
||||
widget.config,
|
||||
widget.homePage,
|
||||
);
|
||||
return const RecoveryPage();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.strings.forgotPassword,
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -304,17 +292,17 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
||||
onTap: () async {
|
||||
final dialog = createProgressDialog(
|
||||
context,
|
||||
context.strings.pleaseWait,
|
||||
context.l10n.pleaseWait,
|
||||
);
|
||||
await dialog.show();
|
||||
await widget.config.logout();
|
||||
await Configuration.instance.logout();
|
||||
await dialog.hide();
|
||||
Navigator.of(context)
|
||||
.popUntil((route) => route.isFirst);
|
||||
},
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.strings.changeEmail,
|
||||
context.l10n.changeEmail,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -3,15 +3,14 @@ import 'dart:io' as io;
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:dotted_border/dotted_border.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_configuration/constants.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/gradient_button.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import "package:ente_ui/theme/ente_theme_data.dart";
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
import 'package:ente_utils/share_utils.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
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';
|
||||
@@ -19,7 +18,6 @@ import 'package:share_plus/share_plus.dart';
|
||||
import 'package:step_progress_indicator/step_progress_indicator.dart';
|
||||
|
||||
class RecoveryKeyPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final bool? showAppBar;
|
||||
final String recoveryKey;
|
||||
final String doneText;
|
||||
@@ -31,7 +29,6 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
final bool showProgressBar;
|
||||
|
||||
const RecoveryKeyPage(
|
||||
this.config,
|
||||
this.recoveryKey,
|
||||
this.doneText, {
|
||||
super.key,
|
||||
@@ -50,15 +47,9 @@ class RecoveryKeyPage extends StatefulWidget {
|
||||
|
||||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
bool _hasTriedToSave = false;
|
||||
late final io.File _recoveryKeyFile;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_recoveryKeyFile = io.File(
|
||||
"${widget.config.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
}
|
||||
final _recoveryKeyFile = io.File(
|
||||
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -82,7 +73,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.strings.recoveryKeyCopiedToClipboard,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
@@ -99,17 +90,17 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
child: StepProgressIndicator(
|
||||
totalSteps: 4,
|
||||
currentStep: 3,
|
||||
selectedColor: getEnteColorScheme(context).alternativeColor,
|
||||
selectedColor: Theme.of(context).colorScheme.alternativeColor,
|
||||
roundedEdges: const Radius.circular(10),
|
||||
unselectedColor:
|
||||
getEnteColorScheme(context).stepProgressUnselectedColor,
|
||||
Theme.of(context).colorScheme.stepProgressUnselectedColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: widget.showAppBar!
|
||||
? AppBar(
|
||||
elevation: 0,
|
||||
title: Text(widget.title ?? context.strings.recoveryKey),
|
||||
title: Text(widget.title ?? context.l10n.recoveryKey),
|
||||
)
|
||||
: null,
|
||||
body: Padding(
|
||||
@@ -130,15 +121,14 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
widget.showAppBar!
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
widget.title ?? context.strings.recoveryKey,
|
||||
widget.title ?? context.l10n.recoveryKey,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.all(widget.showAppBar! ? 0 : 12),
|
||||
),
|
||||
Text(
|
||||
widget.text ??
|
||||
context.strings.recoveryKeyOnForgotPassword,
|
||||
widget.text ?? context.l10n.recoveryKeyOnForgotPassword,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||
@@ -146,14 +136,14 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: LinearGradient(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
getEnteColorScheme(context).primary700,
|
||||
getEnteColorScheme(context).primary300,
|
||||
Color(0x8E9610D6),
|
||||
Color(0x8E9F4FC6),
|
||||
],
|
||||
stops: const [0.0, 0.9753],
|
||||
stops: [0.0, 0.9753],
|
||||
),
|
||||
),
|
||||
child: DottedBorder(
|
||||
@@ -217,7 +207,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Text(
|
||||
widget.subText ??
|
||||
context.strings.recoveryKeySaveDescription,
|
||||
context.l10n.recoveryKeySaveDescription,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
@@ -255,7 +245,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.strings.doThisLater),
|
||||
child: Text(context.l10n.doThisLater),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -267,7 +257,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
onTap: () async {
|
||||
await shareDialog(
|
||||
context,
|
||||
context.strings.recoveryKey,
|
||||
context.l10n.recoveryKey,
|
||||
saveAction: () async {
|
||||
await _saveRecoveryKey(recoveryKey);
|
||||
},
|
||||
@@ -276,7 +266,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
},
|
||||
);
|
||||
},
|
||||
text: context.strings.saveKey,
|
||||
text: context.l10n.saveKey,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -312,7 +302,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
if (mounted) {
|
||||
showToast(
|
||||
context,
|
||||
context.strings.recoveryKeySaved,
|
||||
context.l10n.recoveryKeySaved,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
@@ -1,17 +1,13 @@
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/buttons/dynamic_fab.dart';
|
||||
import 'package:ente_ui/pages/base_home_page.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecoveryPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final BaseHomePage homePage;
|
||||
|
||||
const RecoveryPage(this.config, this.homePage, {super.key});
|
||||
const RecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||
@@ -25,18 +21,16 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await widget.config.recover(_recoveryKey.text.trim());
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
await Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return PopScope(
|
||||
return const PopScope(
|
||||
canPop: false,
|
||||
child: PasswordEntryPage(
|
||||
widget.config,
|
||||
PasswordEntryMode.reset,
|
||||
widget.homePage,
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
@@ -92,7 +86,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
context.strings.forgotPassword,
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -143,7 +137,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
context.strings.noRecoveryKeyTitle,
|
||||
context.l10n.noRecoveryKeyTitle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
@@ -1,24 +1,22 @@
|
||||
import "dart:convert";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:ente_configuration/base_configuration.dart";
|
||||
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/dialog_util.dart";
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import "package:ente_strings/ente_strings.dart";
|
||||
import "package:ente_ui/components/buttons/dynamic_fab.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_ui/utils/dialog_util.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||
|
||||
class RequestPasswordVerificationPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
final OnPasswordVerifiedFn onPasswordVerified;
|
||||
final Function? onPasswordError;
|
||||
|
||||
const RequestPasswordVerificationPage(
|
||||
this.config, {
|
||||
const RequestPasswordVerificationPage({
|
||||
super.key,
|
||||
required this.onPasswordVerified,
|
||||
this.onPasswordError,
|
||||
@@ -41,7 +39,7 @@ class _RequestPasswordVerificationPageState
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
email = widget.config.getEmail();
|
||||
email = Configuration.instance.getEmail();
|
||||
_passwordFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_passwordInFocus = _passwordFocusNode.hasFocus;
|
||||
@@ -78,14 +76,13 @@ class _RequestPasswordVerificationPageState
|
||||
key: const ValueKey("verifyPasswordButton"),
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _passwordController.text.isNotEmpty,
|
||||
buttonText: context.strings.verifyPassword,
|
||||
buttonText: context.l10n.verifyPassword,
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog =
|
||||
createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
final attributes = widget.config.getKeyAttributes()!;
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(_passwordController.text),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
@@ -111,8 +108,8 @@ class _RequestPasswordVerificationPageState
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.incorrectPasswordTitle,
|
||||
context.strings.pleaseTryAgain,
|
||||
context.l10n.incorrectPasswordTitle,
|
||||
context.l10n.pleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -133,7 +130,7 @@ class _RequestPasswordVerificationPageState
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30, left: 20, right: 20),
|
||||
child: Text(
|
||||
context.strings.enterPassword,
|
||||
context.l10n.enterPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
@@ -168,7 +165,7 @@ class _RequestPasswordVerificationPageState
|
||||
key: const ValueKey("passwordInputField"),
|
||||
autofillHints: const [AutofillHints.password],
|
||||
decoration: InputDecoration(
|
||||
hintText: context.strings.enterYourPasswordHint,
|
||||
hintText: context.l10n.enterYourPasswordHint,
|
||||
filled: true,
|
||||
contentPadding: const EdgeInsets.all(20),
|
||||
border: UnderlineInputBorder(
|
||||
@@ -1,20 +1,17 @@
|
||||
import 'package:ente_accounts/ente_accounts.dart';
|
||||
import 'package:ente_configuration/base_configuration.dart';
|
||||
import 'package:ente_strings/ente_strings.dart';
|
||||
import 'package:ente_ui/components/loading_widget.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:ente_ui/utils/toast_util.dart';
|
||||
import 'package:ente_utils/date_time_util.dart';
|
||||
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/models/sessions.dart';
|
||||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/utils/date_time_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:logging/logging.dart';
|
||||
|
||||
class SessionsPage extends StatefulWidget {
|
||||
final BaseConfiguration config;
|
||||
const SessionsPage(
|
||||
this.config, {
|
||||
super.key,
|
||||
});
|
||||
const SessionsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SessionsPage> createState() => _SessionsPageState();
|
||||
@@ -37,7 +34,7 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
title: Text(context.strings.activeSessions),
|
||||
title: Text(context.l10n.activeSessions),
|
||||
),
|
||||
body: _getBody(),
|
||||
);
|
||||
@@ -115,7 +112,7 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
}
|
||||
|
||||
Future<void> _terminateSession(Session session) async {
|
||||
final dialog = createProgressDialog(context, context.strings.pleaseWait);
|
||||
final dialog = createProgressDialog(context, context.l10n.pleaseWait);
|
||||
await dialog.show();
|
||||
try {
|
||||
await UserService.instance.terminateSession(session.token);
|
||||
@@ -127,8 +124,8 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
// ignore: unawaited_futures
|
||||
showErrorDialog(
|
||||
context,
|
||||
context.strings.oops,
|
||||
context.strings.somethingWentWrongPleaseTryAgain,
|
||||
context.l10n.oops,
|
||||
context.l10n.somethingWentWrongPleaseTryAgain,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -150,18 +147,18 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
|
||||
void _showSessionTerminationDialog(Session session) {
|
||||
final isLoggingOutFromThisDevice =
|
||||
session.token == widget.config.getToken();
|
||||
session.token == Configuration.instance.getToken();
|
||||
Widget text;
|
||||
if (isLoggingOutFromThisDevice) {
|
||||
text = Text(
|
||||
context.strings.thisWillLogYouOutOfThisDevice,
|
||||
context.l10n.thisWillLogYouOutOfThisDevice,
|
||||
);
|
||||
} else {
|
||||
text = SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
context.strings.thisWillLogYouOutOfTheFollowingDevice,
|
||||
context.l10n.thisWillLogYouOutOfTheFollowingDevice,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.all(8)),
|
||||
Text(
|
||||
@@ -173,12 +170,12 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
);
|
||||
}
|
||||
final AlertDialog alert = AlertDialog(
|
||||
title: Text(context.strings.terminateSession),
|
||||
title: Text(context.l10n.terminateSession),
|
||||
content: text,
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.terminate,
|
||||
context.l10n.terminate,
|
||||
style: const TextStyle(
|
||||
color: Colors.red,
|
||||
),
|
||||
@@ -194,11 +191,11 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
),
|
||||
TextButton(
|
||||
child: Text(
|
||||
context.strings.cancel,
|
||||
context.l10n.cancel,
|
||||
style: TextStyle(
|
||||
color: isLoggingOutFromThisDevice
|
||||
? getEnteColorScheme(context).alternativeColor
|
||||
: getEnteColorScheme(context).textBase,
|
||||
? Theme.of(context).colorScheme.alternativeColor
|
||||
: Theme.of(context).colorScheme.defaultTextColor,
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
@@ -217,12 +214,12 @@ class _SessionsPageState extends State<SessionsPage> {
|
||||
}
|
||||
|
||||
Widget _getUAWidget(Session session) {
|
||||
if (session.token == widget.config.getToken()) {
|
||||
if (session.token == Configuration.instance.getToken()) {
|
||||
return Text(
|
||||
context.strings.thisDevice,
|
||||
context.l10n.thisDevice,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: getEnteColorScheme(context).alternativeColor,
|
||||
color: Theme.of(context).colorScheme.alternativeColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/onboarding/view/setup_enter_secret_key_page.dart';
|
||||
import 'package:ente_auth/onboarding/view/view_qr_page.dart';
|
||||
import 'package:ente_auth/services/local_authentication_service.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/code_store.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -21,7 +22,6 @@ 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:ente_lock_screen/local_authentication_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/dialog_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum DialogUserChoice { firstChoice, secondChoice }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
@@ -8,7 +9,6 @@ import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/components/models/custom_button_style.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/theme/effects.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
@@ -10,7 +11,6 @@ import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_type.dart';
|
||||
import 'package:ente_auth/ui/components/separators.dart';
|
||||
import 'package:ente_auth/ui/components/text_input_widget.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
///Will return null if dismissed by tapping outside
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/components/menu_item_child_widgets.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/ente_theme.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/ui/components/separators.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/models/execution_states.dart';
|
||||
import 'package:ente_auth/models/typedefs.dart';
|
||||
import 'package:ente_auth/theme/colors.dart';
|
||||
import 'package:ente_auth/ui/common/loading_widget.dart';
|
||||
import 'package:ente_auth/utils/debouncer.dart';
|
||||
import 'package:ente_base/typedefs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef OnChangedCallBack = void Function(bool);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/event_bus.dart';
|
||||
import 'package:ente_auth/events/codes_updated_event.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CoachMarkWidget extends StatelessWidget {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user