diff --git a/auth/.gitignore b/auth/.gitignore
index b909f8341..bcfd8fe6b 100644
--- a/auth/.gitignore
+++ b/auth/.gitignore
@@ -15,6 +15,11 @@
*.iws
.idea/
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
@@ -32,3 +37,4 @@ lib/generated_plugin_registrant.dart
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
android/key.properties
+dist/
\ No newline at end of file
diff --git a/auth/android/app/build.gradle b/auth/android/app/build.gradle
index 7462f4d36..5621b08b6 100644
--- a/auth/android/app/build.gradle
+++ b/auth/android/app/build.gradle
@@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) {
}
android {
- compileSdkVersion 33
+ compileSdkVersion 34
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -46,7 +46,7 @@ android {
defaultConfig {
applicationId "io.ente.auth"
- minSdkVersion 20
+ minSdkVersion 21
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -56,7 +56,7 @@ android {
signingConfigs {
release {
- storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH"))
+ storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null
keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS")
keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD")
storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD")
@@ -109,6 +109,7 @@ dependencies {
implementation 'io.sentry:sentry-android:2.0.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3'
+ implementation 'com.google.guava:guava:28.2-android'
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
diff --git a/auth/distribute_options.yaml b/auth/distribute_options.yaml
new file mode 100644
index 000000000..9634f4cbd
--- /dev/null
+++ b/auth/distribute_options.yaml
@@ -0,0 +1,25 @@
+output: dist/
+
+releases:
+ - name: dev
+ jobs:
+ - name: release-dev-linux-zip
+ package:
+ platform: linux
+ target: zip
+ - name: release-dev-linux-deb
+ package:
+ platform: linux
+ target: deb
+ - name: release-dev-linux-appimage
+ package:
+ platform: linux
+ target: appimage
+ - name: release-dev-windows-exe
+ package:
+ platform: windows
+ target: exe
+ - name: release-dev-macos-dmg
+ package:
+ platform: macos
+ target: dmg
diff --git a/auth/ios/Podfile.lock b/auth/ios/Podfile.lock
index dc7d8d043..c70156377 100644
--- a/auth/ios/Podfile.lock
+++ b/auth/ios/Podfile.lock
@@ -1,7 +1,9 @@
PODS:
- - connectivity (0.0.1):
+ - app_links (0.0.1):
- Flutter
- - Reachability
+ - connectivity_plus (0.0.1):
+ - Flutter
+ - ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.4):
@@ -45,27 +47,24 @@ PODS:
- Flutter (1.0.0)
- flutter_email_sender (0.0.1):
- Flutter
- - flutter_inappwebview (0.0.1):
+ - flutter_inappwebview_ios (0.0.1):
- Flutter
- - flutter_inappwebview/Core (= 0.0.1)
+ - flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- - flutter_inappwebview/Core (0.0.1):
+ - flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
+ - flutter_local_authentication (1.2.0):
+ - Flutter
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_native_splash (0.0.1):
- Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- - flutter_sodium (0.0.1):
- - Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- - FMDB (2.7.5):
- - FMDB/standard (= 2.7.5)
- - FMDB/standard (2.7.5)
- local_auth_ios (0.0.1):
- Flutter
- move_to_background (0.0.1):
@@ -84,45 +83,63 @@ PODS:
- qr_code_scanner (0.2.0):
- Flutter
- MTBBarcodeScanner
- - Reachability (3.2)
- - SDWebImage (5.17.0):
- - SDWebImage/Core (= 5.17.0)
- - SDWebImage/Core (5.17.0)
- - Sentry/HybridSDK (8.9.1):
- - SentryPrivate (= 8.9.1)
+ - ReachabilitySwift (5.0.0)
+ - SDWebImage (5.18.10):
+ - SDWebImage/Core (= 5.18.10)
+ - SDWebImage/Core (5.18.10)
+ - Sentry/HybridSDK (8.19.0):
+ - SentryPrivate (= 8.19.0)
- sentry_flutter (0.0.1):
- Flutter
- FlutterMacOS
- - Sentry/HybridSDK (= 8.9.1)
- - SentryPrivate (8.9.1)
+ - Sentry/HybridSDK (= 8.19.0)
+ - SentryPrivate (8.19.0)
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
+ - smart_auth (0.0.1):
+ - Flutter
+ - sodium_libs (2.2.0):
+ - Flutter
- sqflite (0.0.3):
- Flutter
- - FMDB (>= 2.7.5)
- - SwiftyGif (5.4.4)
- - Toast (4.0.0)
- - uni_links (0.0.1):
+ - FlutterMacOS
+ - sqlite3 (3.45.1):
+ - sqlite3/common (= 3.45.1)
+ - sqlite3/common (3.45.1)
+ - sqlite3/fts5 (3.45.1):
+ - sqlite3/common
+ - sqlite3/perf-threadsafe (3.45.1):
+ - sqlite3/common
+ - sqlite3/rtree (3.45.1):
+ - sqlite3/common
+ - sqlite3_flutter_libs (0.0.1):
- Flutter
+ - sqlite3 (~> 3.45.1)
+ - sqlite3/fts5
+ - sqlite3/perf-threadsafe
+ - sqlite3/rtree
+ - SwiftyGif (5.4.4)
+ - Toast (4.1.0)
- url_launcher_ios (0.0.1):
- Flutter
DEPENDENCIES:
- - connectivity (from `.symlinks/plugins/connectivity/ios`)
+ - app_links (from `.symlinks/plugins/app_links/ios`)
+ - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- file_saver (from `.symlinks/plugins/file_saver/ios`)
- fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`)
- Flutter (from `Flutter`)
- flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`)
- - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
+ - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
+ - flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- - flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
- move_to_background (from `.symlinks/plugins/move_to_background/ios`)
@@ -134,27 +151,31 @@ DEPENDENCIES:
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- - sqflite (from `.symlinks/plugins/sqflite/ios`)
- - uni_links (from `.symlinks/plugins/uni_links/ios`)
+ - smart_auth (from `.symlinks/plugins/smart_auth/ios`)
+ - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`)
+ - sqflite (from `.symlinks/plugins/sqflite/darwin`)
+ - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS:
trunk:
- DKImagePickerController
- DKPhotoGallery
- - FMDB
- MTBBarcodeScanner
- OrderedSet
- - Reachability
+ - ReachabilitySwift
- SDWebImage
- Sentry
- SentryPrivate
+ - sqlite3
- SwiftyGif
- Toast
EXTERNAL SOURCES:
- connectivity:
- :path: ".symlinks/plugins/connectivity/ios"
+ app_links:
+ :path: ".symlinks/plugins/app_links/ios"
+ connectivity_plus:
+ :path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
@@ -167,16 +188,16 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_email_sender:
:path: ".symlinks/plugins/flutter_email_sender/ios"
- flutter_inappwebview:
- :path: ".symlinks/plugins/flutter_inappwebview/ios"
+ flutter_inappwebview_ios:
+ :path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
+ flutter_local_authentication:
+ :path: ".symlinks/plugins/flutter_local_authentication/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
- flutter_sodium:
- :path: ".symlinks/plugins/flutter_sodium/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
local_auth_ios:
@@ -199,51 +220,58 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
+ smart_auth:
+ :path: ".symlinks/plugins/smart_auth/ios"
+ sodium_libs:
+ :path: ".symlinks/plugins/sodium_libs/ios"
sqflite:
- :path: ".symlinks/plugins/sqflite/ios"
- uni_links:
- :path: ".symlinks/plugins/uni_links/ios"
+ :path: ".symlinks/plugins/sqflite/darwin"
+ sqlite3_flutter_libs:
+ :path: ".symlinks/plugins/sqlite3_flutter_libs/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS:
- connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
- device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
+ app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
+ connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
+ device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
- file_picker: ce3938a0df3cc1ef404671531facef740d03f920
+ file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545
- Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
+ Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
- flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62
- flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743
+ flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0
+ flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb
+ flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
- flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
- FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
- local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605
+ local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
- package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
- path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
+ package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
+ path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e
- Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
- SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9
- Sentry: e3203780941722a1fcfee99e351de14244c7f806
- sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c
- SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13
+ ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
+ SDWebImage: fc8f2d48bbfd72ef39d70e981bd24a3f3be53fec
+ Sentry: 1ebcaef678a27c8ac515f974cb5425dd1bbdec2f
+ sentry_flutter: ecdfbedee55337205561cfa782ee02d31ec83e1f
+ SentryPrivate: 765c9b4ebe9ac1a5fcdc067c5a1cfbf3f10e1677
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
- shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
- sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
+ shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
+ smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2
+ sodium_libs: 0486eb2c3172ce494406367d4b379042444b769d
+ sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
+ sqlite3: 73b7fc691fdc43277614250e04d183740cb15078
+ sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
- Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
- uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
- url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
+ Toast: ec33c32b8688982cecc6348adeae667c1b9938da
+ url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb
diff --git a/auth/ios/Runner/AppDelegate.swift b/auth/ios/Runner/AppDelegate.swift
index 70693e4a8..3fdfc7389 100644
--- a/auth/ios/Runner/AppDelegate.swift
+++ b/auth/ios/Runner/AppDelegate.swift
@@ -1,5 +1,6 @@
-import UIKit
import Flutter
+import UIKit
+import app_links
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@@ -8,6 +9,15 @@ import Flutter
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
- return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+
+ super.application(application, didFinishLaunchingWithOptions: launchOptions)
+
+ if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
+ AppLinks.shared.handleLink(url: url)
+ }
+
+ return false
+
+ // return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
diff --git a/auth/ios/Runner/Info.plist b/auth/ios/Runner/Info.plist
index 5d24c4cee..28984c705 100644
--- a/auth/ios/Runner/Info.plist
+++ b/auth/ios/Runner/Info.plist
@@ -77,5 +77,9 @@
UIViewControllerBasedStatusBarAppearance
+ LSSupportsOpeningDocumentsInPlace
+
+ UIFileSharingEnabled
+
diff --git a/auth/lib/app/view/app.dart b/auth/lib/app/view/app.dart
index b723e54b9..c8e9051e6 100644
--- a/auth/lib/app/view/app.dart
+++ b/auth/lib/app/view/app.dart
@@ -20,7 +20,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
class App extends StatefulWidget {
final Locale locale;
- const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
+ const App({super.key, this.locale = const Locale("en")});
static void setLocale(BuildContext context, Locale newLocale) {
_AppState state = context.findAncestorStateOfType<_AppState>()!;
diff --git a/auth/lib/core/configuration.dart b/auth/lib/core/configuration.dart
index 3e6540af6..0dc84ba12 100644
--- a/auth/lib/core/configuration.dart
+++ b/auth/lib/core/configuration.dart
@@ -12,12 +12,12 @@ import 'package:ente_auth/models/key_attributes.dart';
import 'package:ente_auth/models/key_gen_result.dart';
import 'package:ente_auth/models/private_key_attributes.dart';
import 'package:ente_auth/store/authenticator_db.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:tuple/tuple.dart';
class Configuration {
@@ -70,9 +70,10 @@ class Configuration {
Future init() async {
_preferences = await SharedPreferences.getInstance();
+ sqfliteFfiInit();
_secureStorage = const FlutterSecureStorage();
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
- _tempDirectory = _documentsDirectory + "/temp/";
+ _tempDirectory = "$_documentsDirectory/temp/";
final tempDirectory = io.Directory(_tempDirectory);
try {
final currentTime = DateTime.now().microsecondsSinceEpoch;
@@ -160,7 +161,7 @@ class Configuration {
// decrypt the master key
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -170,28 +171,28 @@ class Configuration {
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
// Generate a public-private keypair and encrypt the latter
- final keyPair = await CryptoUtil.generateKeyPair();
+ final keyPair = CryptoUtil.generateKeyPair();
final encryptedSecretKeyData =
- CryptoUtil.encryptSync(keyPair.sk, masterKey);
+ CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
final attributes = KeyAttributes(
- Sodium.bin2base64(kekSalt),
- Sodium.bin2base64(encryptedKeyData.encryptedData!),
- Sodium.bin2base64(encryptedKeyData.nonce!),
- Sodium.bin2base64(keyPair.pk),
- Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
- Sodium.bin2base64(encryptedSecretKeyData.nonce!),
+ CryptoUtil.bin2base64(kekSalt),
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedKeyData.nonce!),
+ CryptoUtil.bin2base64(keyPair.publicKey),
+ CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
derivedKeyResult.memLimit,
derivedKeyResult.opsLimit,
- Sodium.bin2base64(encryptedMasterKey.encryptedData!),
- Sodium.bin2base64(encryptedMasterKey.nonce!),
- Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
- Sodium.bin2base64(encryptedRecoveryKey.nonce!),
+ CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
+ CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
+ CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
+ CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
);
final privateAttributes = PrivateKeyAttributes(
- Sodium.bin2base64(masterKey),
- Sodium.bin2hex(recoveryKey),
- Sodium.bin2base64(keyPair.sk),
+ CryptoUtil.bin2base64(masterKey),
+ CryptoUtil.bin2hex(recoveryKey),
+ CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
);
return KeyGenResult(attributes, privateAttributes, loginKey);
}
@@ -206,7 +207,7 @@ class Configuration {
// decrypt the master key
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
@@ -218,9 +219,9 @@ class Configuration {
final existingAttributes = getKeyAttributes();
final updatedAttributes = existingAttributes!.copyWith(
- kekSalt: Sodium.bin2base64(kekSalt),
- encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
- keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
+ kekSalt: CryptoUtil.bin2base64(kekSalt),
+ encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
memLimit: derivedKeyResult.memLimit,
opsLimit: derivedKeyResult.opsLimit,
);
@@ -238,8 +239,8 @@ class Configuration {
}) async {
_logger.info('Start decryptAndSaveSecrets');
keyEncryptionKey ??= await CryptoUtil.deriveKey(
- utf8.encode(password) as Uint8List,
- Sodium.base642bin(attributes.kekSalt),
+ utf8.encode(password),
+ CryptoUtil.base642bin(attributes.kekSalt),
attributes.memLimit,
attributes.opsLimit,
);
@@ -248,31 +249,31 @@ class Configuration {
Uint8List key;
try {
key = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedKey),
+ CryptoUtil.base642bin(attributes.encryptedKey),
keyEncryptionKey,
- Sodium.base642bin(attributes.keyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.keyDecryptionNonce),
);
} catch (e) {
_logger.severe('master-key failed, incorrect password?', e);
throw Exception("Incorrect password");
}
_logger.info("master-key done");
- await setKey(Sodium.bin2base64(key));
+ await setKey(CryptoUtil.bin2base64(key));
final secretKey = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedSecretKey),
+ CryptoUtil.base642bin(attributes.encryptedSecretKey),
key,
- Sodium.base642bin(attributes.secretKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
);
_logger.info("secret-key done");
- await setSecretKey(Sodium.bin2base64(secretKey));
+ await setSecretKey(CryptoUtil.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
- Sodium.base642bin(getEncryptedToken()!),
- Sodium.base642bin(attributes.publicKey),
+ CryptoUtil.base642bin(getEncryptedToken()!),
+ CryptoUtil.base642bin(attributes.publicKey),
secretKey,
);
_logger.info('appToken done');
await setToken(
- Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
+ CryptoUtil.bin2base64(token, urlSafe: true),
);
return keyEncryptionKey;
}
@@ -291,28 +292,28 @@ class Configuration {
Uint8List masterKey;
try {
masterKey = await CryptoUtil.decrypt(
- Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
- Sodium.hex2bin(recoveryKey),
- Sodium.base642bin(attributes.masterKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
+ CryptoUtil.hex2bin(recoveryKey),
+ CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
);
} catch (e) {
_logger.severe(e);
rethrow;
}
- await setKey(Sodium.bin2base64(masterKey));
+ await setKey(CryptoUtil.bin2base64(masterKey));
final secretKey = CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedSecretKey),
+ CryptoUtil.base642bin(attributes.encryptedSecretKey),
masterKey,
- Sodium.base642bin(attributes.secretKeyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
);
- await setSecretKey(Sodium.bin2base64(secretKey));
+ await setSecretKey(CryptoUtil.bin2base64(secretKey));
final token = CryptoUtil.openSealSync(
- Sodium.base642bin(getEncryptedToken()!),
- Sodium.base642bin(attributes.publicKey),
+ CryptoUtil.base642bin(getEncryptedToken()!),
+ CryptoUtil.base642bin(attributes.publicKey),
secretKey,
);
await setToken(
- Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
+ CryptoUtil.bin2base64(token, urlSafe: true),
);
}
@@ -400,27 +401,31 @@ class Configuration {
}
Uint8List? getKey() {
- return _key == null ? null : Sodium.base642bin(_key!);
+ return _key == null ? null : CryptoUtil.base642bin(_key!);
}
Uint8List? getSecretKey() {
- return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
+ return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
}
Uint8List? getAuthSecretKey() {
- return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
+ return _authSecretKey == null
+ ? null
+ : CryptoUtil.base642bin(_authSecretKey!);
}
Uint8List? getOfflineSecretKey() {
- return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
+ return _offlineAuthKey == null
+ ? null
+ : CryptoUtil.base642bin(_offlineAuthKey!);
}
Uint8List getRecoveryKey() {
final keyAttributes = getKeyAttributes()!;
return CryptoUtil.decryptSync(
- Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
+ CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
getKey()!,
- Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
+ CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
);
}
@@ -447,7 +452,7 @@ class Configuration {
iOptions: _secureStorageOptionsIOS,
);
} else {
- _offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
+ _offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
await _secureStorage.write(
key: offlineAuthSecretKey,
value: _offlineAuthKey,
diff --git a/auth/lib/core/constants.dart b/auth/lib/core/constants.dart
index dcc111c4b..de39eb301 100644
--- a/auth/lib/core/constants.dart
+++ b/auth/lib/core/constants.dart
@@ -7,7 +7,7 @@ const String sentryDSN =
"https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9";
const String sentryTunnel = "https://sentry-reporter.ente.io";
const String roadmapURL = "https://roadmap.ente.io";
-const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions";
+const String githubIssuesUrl = "https://github.com/ente-io/auth/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc";
const int microSecondsInDay = 86400000000;
const int android11SDKINT = 30;
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
diff --git a/auth/lib/core/errors.dart b/auth/lib/core/errors.dart
index c1e83cde0..ba1310b6c 100644
--- a/auth/lib/core/errors.dart
+++ b/auth/lib/core/errors.dart
@@ -1,9 +1,9 @@
class InvalidFileError extends ArgumentError {
- InvalidFileError(String message) : super(message);
+ InvalidFileError(String super.message);
}
class InvalidFileUploadState extends AssertionError {
- InvalidFileUploadState(String message) : super(message);
+ InvalidFileUploadState(String super.message);
}
class SubscriptionAlreadyClaimedError extends Error {}
@@ -30,19 +30,15 @@ class UnauthorizedError extends Error {}
class RequestCancelledError extends Error {}
class InvalidSyncStatusError extends AssertionError {
- InvalidSyncStatusError(String message) : super(message);
+ InvalidSyncStatusError(String super.message);
}
class UnauthorizedEditError extends AssertionError {}
class InvalidStateError extends AssertionError {
- InvalidStateError(String message) : super(message);
+ InvalidStateError(String super.message);
}
-class KeyDerivationError extends Error {}
-
-class LoginKeyDerivationError extends Error {}
-
class SrpSetupNotCompleteError extends Error {}
class AuthenticatorKeyNotFound extends Error {}
diff --git a/auth/lib/core/logging/super_logging.dart b/auth/lib/core/logging/super_logging.dart
index 7b62d0a70..224ba553a 100644
--- a/auth/lib/core/logging/super_logging.dart
+++ b/auth/lib/core/logging/super_logging.dart
@@ -235,14 +235,14 @@ class SuperLogging {
extraLines = null;
}
- final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
+ final str = "${config.prefix} ${rec.toPrettyString(extraLines)}";
// write to stdout
printLog(str);
// push to log queue
if (fileIsEnabled) {
- fileQueueEntries.add(str + '\n');
+ fileQueueEntries.add('$str\n');
if (fileQueueEntries.length == 1) {
flushQueue();
}
@@ -275,7 +275,7 @@ class SuperLogging {
static var logChunkSize = 800;
static void printLog(String text) {
- text.chunked(logChunkSize).forEach(print);
+ text.chunked(logChunkSize).forEach(debugPrint);
}
/// A queue to be consumed by [setupSentry].
@@ -354,7 +354,7 @@ class SuperLogging {
final date = config.dateFmt!.parse(basename(file.path));
dates[file as File] = date;
files.add(file);
- } on FormatException {}
+ } on Exception catch (_) {}
}
final nowTime = DateTime.now();
@@ -374,7 +374,7 @@ class SuperLogging {
"deleting log file ${file.path}",
);
await file.delete();
- } catch (ignore) {}
+ } on Exception catch (_) {}
}
}
diff --git a/auth/lib/core/logging/tunneled_transport.dart b/auth/lib/core/logging/tunneled_transport.dart
index 1191adc2e..f9ffd5ad1 100644
--- a/auth/lib/core/logging/tunneled_transport.dart
+++ b/auth/lib/core/logging/tunneled_transport.dart
@@ -46,7 +46,7 @@ class TunneledTransport implements Transport {
_options.logger(
SentryLevel.error,
'API returned an error, statusCode = ${response.statusCode}, '
- 'body = ${response.body}',
+ 'body = ${response.body}',
);
}
return const SentryId.empty();
@@ -65,8 +65,8 @@ class TunneledTransport implements Transport {
}
Future _createStreamedRequest(
- SentryEnvelope envelope,
- ) async {
+ SentryEnvelope envelope,
+ ) async {
final streamedRequest = StreamedRequest('POST', _tunnel);
envelope
.envelopeStream(_options)
@@ -91,10 +91,10 @@ class _CredentialBuilder {
_clock = clock;
factory _CredentialBuilder(
- Dsn? dsn,
- String sdkIdentifier,
- ClockProvider clock,
- ) {
+ Dsn? dsn,
+ String sdkIdentifier,
+ ClockProvider clock,
+ ) {
final authHeader = _buildAuthHeader(
publicKey: dsn?.publicKey,
secretKey: dsn?.secretKey,
diff --git a/auth/lib/core/network.dart b/auth/lib/core/network.dart
index d901268d6..087d6acca 100644
--- a/auth/lib/core/network.dart
+++ b/auth/lib/core/network.dart
@@ -3,9 +3,10 @@ import 'dart:io';
import 'package:dio/dio.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
+import 'package:ente_auth/utils/package_info_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:fk_user_agent/fk_user_agent.dart';
import 'package:flutter/foundation.dart';
-import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:uuid/uuid.dart';
@@ -21,16 +22,22 @@ class Network {
late Dio _enteDio;
Future init() async {
- await FkUserAgent.init();
- final packageInfo = await PackageInfo.fromPlatform();
+ if (PlatformUtil.isMobile()) await FkUserAgent.init();
+ final packageInfo = await PackageInfoUtil().getPackageInfo();
+ final version = PackageInfoUtil().getVersion(packageInfo);
+ final packageName = PackageInfoUtil().getPackageName(packageInfo);
+
final preferences = await SharedPreferences.getInstance();
+
_dio = Dio(
BaseOptions(
- connectTimeout: kConnectTimeout,
+ connectTimeout: Duration(milliseconds: kConnectTimeout),
headers: {
- HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
- 'X-Client-Version': packageInfo.version,
- 'X-Client-Package': packageInfo.packageName,
+ HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
+ ? FkUserAgent.userAgent
+ : Platform.operatingSystem,
+ 'X-Client-Version': version,
+ 'X-Client-Package': packageName,
},
),
);
@@ -38,11 +45,14 @@ class Network {
_enteDio = Dio(
BaseOptions(
baseUrl: apiEndpoint,
- connectTimeout: kConnectTimeout,
+ connectTimeout: Duration(milliseconds: kConnectTimeout),
headers: {
- HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
- 'X-Client-Version': packageInfo.version,
- 'X-Client-Package': packageInfo.packageName,
+ if (PlatformUtil.isMobile())
+ HttpHeaders.userAgentHeader: FkUserAgent.userAgent
+ else
+ HttpHeaders.userAgentHeader: Platform.operatingSystem,
+ 'X-Client-Version': version,
+ 'X-Client-Package': packageName,
},
),
);
@@ -76,8 +86,8 @@ class EnteRequestInterceptor extends Interceptor {
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (kDebugMode) {
assert(
- options.baseUrl == enteEndpoint,
- "interceptor should only be used for API endpoint",
+ options.baseUrl == enteEndpoint,
+ "interceptor should only be used for API endpoint",
);
}
// ignore: prefer_const_constructors
diff --git a/auth/lib/ente_theme_data.dart b/auth/lib/ente_theme_data.dart
index bb2a994c4..057504cbd 100644
--- a/auth/lib/ente_theme_data.dart
+++ b/auth/lib/ente_theme_data.dart
@@ -11,6 +11,7 @@ final lightThemeData = ThemeData(
iconTheme: const IconThemeData(color: Colors.black),
primaryIconTheme:
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
+ buttonTheme: const ButtonThemeData(),
outlinedButtonTheme: buildOutlinedButtonThemeData(
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
@@ -72,24 +73,42 @@ final lightThemeData = ThemeData(
? const Color.fromRGBO(255, 255, 255, 1)
: const Color.fromRGBO(0, 0, 0, 1);
}),
- ), radioTheme: RadioThemeData(
- fillColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), switchTheme: SwitchThemeData(
- thumbColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- trackColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), colorScheme: const ColorScheme.light(
+ ),
+ radioTheme: RadioThemeData(
+ fillColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ trackColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ colorScheme: const ColorScheme.light(
primary: Colors.black,
secondary: Color.fromARGB(255, 163, 163, 163),
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
@@ -105,6 +124,7 @@ final darkThemeData = ThemeData(
hintColor: const Color.fromRGBO(158, 158, 158, 1),
buttonTheme: const ButtonThemeData().copyWith(
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
+ height: 56,
),
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
outlinedButtonTheme: buildOutlinedButtonThemeData(
@@ -164,24 +184,43 @@ final darkThemeData = ThemeData(
return const Color.fromRGBO(158, 158, 158, 1);
}
}),
- ), radioTheme: RadioThemeData(
- fillColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), switchTheme: SwitchThemeData(
- thumbColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- trackColor: MaterialStateProperty.resolveWith((Set states) {
- if (states.contains(MaterialState.disabled)) { return null; }
- if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
- return null;
- }),
- ), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
+ ),
+ radioTheme: RadioThemeData(
+ fillColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ switchTheme: SwitchThemeData(
+ thumbColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ trackColor:
+ MaterialStateProperty.resolveWith((Set states) {
+ if (states.contains(MaterialState.disabled)) {
+ return null;
+ }
+ if (states.contains(MaterialState.selected)) {
+ return const Color.fromRGBO(102, 187, 106, 1);
+ }
+ return null;
+ }),
+ ),
+ colorScheme: const ColorScheme.dark(primary: Colors.white)
+ .copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
);
TextTheme _buildTextTheme(Color textColor) {
@@ -400,6 +439,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
+ fixedSize: const Size.fromHeight(56),
alignment: Alignment.center,
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
textStyle: const TextStyle(
@@ -436,7 +476,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
}) {
return ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
- foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
+ foregroundColor: onPrimary,
+ backgroundColor: primary,
+ elevation: elevation,
alignment: Alignment.center,
textStyle: const TextStyle(
fontWeight: FontWeight.w600,
diff --git a/auth/lib/gateway/authenticator.dart b/auth/lib/gateway/authenticator.dart
index 1ea1bee32..c51b84db5 100644
--- a/auth/lib/gateway/authenticator.dart
+++ b/auth/lib/gateway/authenticator.dart
@@ -10,12 +10,12 @@ class AuthenticatorGateway {
late String _basedEndpoint;
AuthenticatorGateway(this._dio, this._config) {
- _basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
+ _basedEndpoint = "${_config.getHttpEndpoint()}/authenticator";
}
Future createKey(String encKey, String header) async {
await _dio.post(
- _basedEndpoint + "/key",
+ "$_basedEndpoint/key",
data: {
"encryptedKey": encKey,
"header": header,
@@ -31,7 +31,7 @@ class AuthenticatorGateway {
Future getKey() async {
try {
final response = await _dio.get(
- _basedEndpoint + "/key",
+ "$_basedEndpoint/key",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -39,7 +39,7 @@ class AuthenticatorGateway {
),
);
return AuthKey.fromMap(response.data);
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
throw AuthenticatorKeyNotFound();
} else {
@@ -52,7 +52,7 @@ class AuthenticatorGateway {
Future createEntity(String encryptedData, String header) async {
final response = await _dio.post(
- _basedEndpoint + "/entity",
+ "$_basedEndpoint/entity",
data: {
"encryptedData": encryptedData,
"header": header,
@@ -72,7 +72,7 @@ class AuthenticatorGateway {
String header,
) async {
await _dio.put(
- _basedEndpoint + "/entity",
+ "$_basedEndpoint/entity",
data: {
"id": id,
"encryptedData": encryptedData,
@@ -90,7 +90,7 @@ class AuthenticatorGateway {
String id,
) async {
await _dio.delete(
- _basedEndpoint + "/entity",
+ "$_basedEndpoint/entity",
queryParameters: {
"id": id,
},
@@ -105,7 +105,7 @@ class AuthenticatorGateway {
Future> getDiff(int sinceTime, {int limit = 500}) async {
try {
final response = await _dio.get(
- _basedEndpoint + "/entity/diff",
+ "$_basedEndpoint/entity/diff",
queryParameters: {
"sinceTime": sinceTime,
"limit": limit,
@@ -124,7 +124,7 @@ class AuthenticatorGateway {
}
return authEntities;
} catch (e) {
- if (e is DioError && e.response?.statusCode == 401) {
+ if (e is DioException && e.response?.statusCode == 401) {
throw UnauthorizedError();
} else {
rethrow;
diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb
index da9332daa..e2572b7ca 100644
--- a/auth/lib/l10n/arb/app_en.arb
+++ b/auth/lib/l10n/arb/app_en.arb
@@ -59,7 +59,7 @@
}
},
"contactSupport": "Contact support",
- "rateUsOnStore" : "Rate us on {storeName}",
+ "rateUsOnStore": "Rate us on {storeName}",
"blog": "Blog",
"merchandise": "Merchandise",
"verifyPassword": "Verify password",
@@ -133,7 +133,6 @@
"faq_q_5": "How can I enable FaceID lock in ente Auth",
"faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.",
"somethingWentWrongMessage": "Something went wrong, please try again",
-
"leaveFamily": "Leave family",
"leaveFamilyMessage": "Are you sure that you want to leave the family plan?",
"inFamilyPlanMessage": "You are on a family plan!",
@@ -198,6 +197,10 @@
"recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.",
"doThisLater": "Do this later",
"saveKey": "Save key",
+ "save": "Save",
+ "send": "Send",
+ "saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?",
+ "saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?",
"back": "Back",
"createAccount": "Create account",
"passwordStrength": "Password strength: {passwordStrengthValue}",
@@ -337,10 +340,10 @@
"offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.",
"showLargeIcons": "Show large icons",
"shouldHideCode": "Hide codes",
- "doubleTapToViewHiddenCode" : "You can double tap on an entry to view code",
+ "doubleTapToViewHiddenCode": "You can double tap on an entry to view code",
"focusOnSearchBar": "Focus search on app start",
"confirmUpdatingkey": "Are you sure you want to update the secret key?",
- "minimizeAppOnCopy": "Minimize app on copy",
+ "minimizeAppOnCopy": "Minimize app on copy",
"editCodeAuthMessage": "Authenticate to edit code",
"deleteCodeAuthMessage": "Authenticate to delete code",
"showQRAuthMessage": "Authenticate to show QR code",
@@ -405,5 +408,6 @@
"signOutOtherDevices": "Sign out other devices",
"doNotSignOut": "Do not sign out",
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
- "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!"
-}
+ "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
+ "recoveryKeySaved": "Recovery key saved in Downloads folder!"
+}
\ No newline at end of file
diff --git a/auth/lib/main.dart b/auth/lib/main.dart
index 1dc7ff688..15fcab4a4 100644
--- a/auth/lib/main.dart
+++ b/auth/lib/main.dart
@@ -1,5 +1,6 @@
+import 'dart:io';
+
import 'package:adaptive_theme/adaptive_theme.dart';
-import 'package:computer/computer.dart';
import "package:ente_auth/app/view/app.dart";
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/constants.dart';
@@ -18,7 +19,9 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/ui/tools/lock_screen.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/window_protocol_handler.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/foundation.dart';
import "package:flutter/material.dart";
import 'package:flutter/scheduler.dart';
@@ -26,14 +29,27 @@ import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:privacy_screen/privacy_screen.dart';
+import 'package:window_manager/window_manager.dart';
final _logger = Logger("main");
void main() async {
WidgetsFlutterBinding.ensureInitialized();
+
+ await windowManager.ensureInitialized();
+
+ if (PlatformUtil.isDesktop()) {
+ WindowOptions windowOptions = const WindowOptions(
+ size: Size(450, 800),
+ );
+ windowManager.waitUntilReadyToShow(windowOptions, () async {
+ await windowManager.show();
+ await windowManager.focus();
+ });
+ }
await _runInForeground();
await _setupPrivacyScreen();
- FlutterDisplayMode.setHighRefreshRate();
+ if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate();
}
Future _runInForeground() async {
@@ -65,10 +81,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) {
}
Future _runWithLogs(Function() function, {String prefix = ""}) async {
+ String dir = "";
+ try {
+ dir = "${(await getApplicationSupportDirectory()).path}/logs";
+ } catch (_) {}
await SuperLogging.main(
LogConfig(
body: function,
- logDirPath: (await getApplicationSupportDirectory()).path + "/logs",
+ logDirPath: dir,
maxLogFiles: 5,
sentryDsn: sentryDSN,
enableInDebugMode: true,
@@ -77,10 +97,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async {
);
}
+void _registerWindowsProtocol() {
+ const kWindowsScheme = 'ente';
+ // Register our protocol only on Windows platform
+ if (!kIsWeb && Platform.isWindows) {
+ WindowsProtocolHandler()
+ .register(kWindowsScheme, executable: null, arguments: null);
+ }
+}
+
Future _init(bool bool, {String? via}) async {
- // Start workers asynchronously. No need to wait for them to start
- Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode);
- CryptoUtil.init();
+ _registerWindowsProtocol();
+ await initCryptoUtil();
+
await PreferenceService.instance.init();
await CodeStore.instance.init();
await Configuration.instance.init();
@@ -95,6 +124,7 @@ Future _init(bool bool, {String? via}) async {
}
Future _setupPrivacyScreen() async {
+ if (!PlatformUtil.isMobile()) return;
final brightness =
SchedulerBinding.instance.platformDispatcher.platformBrightness;
bool isInDarkMode = brightness == Brightness.dark;
diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart
index 40c8c6455..fb6289d63 100644
--- a/auth/lib/models/code.dart
+++ b/auth/lib/models/code.dart
@@ -57,14 +57,7 @@ class Code {
updatedAlgo,
updatedType,
updatedCounter,
- "otpauth://${updatedType.name}/" +
- updateIssuer +
- ":" +
- updateAccount +
- "?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
- updateIssuer +
- "&period=$updatePeriod&secret=" +
- updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
+ "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
generatedID: generatedID,
);
}
@@ -83,14 +76,7 @@ class Code {
Algorithm.sha1,
Type.totp,
0,
- "otpauth://totp/" +
- issuer +
- ":" +
- account +
- "?algorithm=SHA1&digits=6&issuer=" +
- issuer +
- "&period=30&secret=" +
- secret,
+ "otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
);
}
diff --git a/auth/lib/models/derived_key_result.dart b/auth/lib/models/derived_key_result.dart
deleted file mode 100644
index a071fb1f8..000000000
--- a/auth/lib/models/derived_key_result.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-import 'dart:typed_data';
-
-class DerivedKeyResult {
- final Uint8List key;
- final int memLimit;
- final int opsLimit;
-
- DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
-}
diff --git a/auth/lib/models/encryption_result.dart b/auth/lib/models/encryption_result.dart
deleted file mode 100644
index 9da16c573..000000000
--- a/auth/lib/models/encryption_result.dart
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'dart:typed_data';
-
-class EncryptionResult {
- final Uint8List? encryptedData;
- final Uint8List? key;
- final Uint8List? header;
- final Uint8List? nonce;
-
- EncryptionResult({
- this.encryptedData,
- this.key,
- this.header,
- this.nonce,
- });
-}
diff --git a/auth/lib/onboarding/view/onboarding_page.dart b/auth/lib/onboarding/view/onboarding_page.dart
index 78bf4e589..0117bdcaf 100644
--- a/auth/lib/onboarding/view/onboarding_page.dart
+++ b/auth/lib/onboarding/view/onboarding_page.dart
@@ -7,26 +7,18 @@ import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/events/trigger_logout_event.dart';
import "package:ente_auth/l10n/l10n.dart";
import 'package:ente_auth/locale.dart';
-import 'package:ente_auth/theme/text_style.dart';
-import 'package:ente_auth/ui/account/email_entry_page.dart';
import 'package:ente_auth/ui/account/login_page.dart';
import 'package:ente_auth/ui/account/logout_dialog.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/account/password_reentry_page.dart';
-import 'package:ente_auth/ui/common/gradient_button.dart';
-import 'package:ente_auth/ui/components/buttons/button_widget.dart';
-import 'package:ente_auth/ui/components/models/button_result.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/settings/language_picker.dart';
-import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
-import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/foundation.dart';
import "package:flutter/material.dart";
-import 'package:local_auth/local_auth.dart';
class OnboardingPage extends StatefulWidget {
- const OnboardingPage({Key? key}) : super(key: key);
+ const OnboardingPage({super.key});
@override
State createState() => _OnboardingPageState();
@@ -56,114 +48,128 @@ class _OnboardingPageState extends State {
final l10n = context.l10n;
return Scaffold(
body: SafeArea(
- child: Center(
- child: SingleChildScrollView(
- child: Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
- child: Column(
- children: [
- Column(
- children: [
- kDebugMode
- ? GestureDetector(
- child: const Align(
- alignment: Alignment.topRight,
- child: Text("Lang"),
- ),
- onTap: () async {
- final locale = await getLocale();
- routeToPage(
- context,
- LanguageSelectorPage(
- appSupportedLocales,
- (locale) async {
- await setLocale(locale);
- App.setLocale(context, locale);
- },
- locale,
+ child: SingleChildScrollView(
+ child: Center(
+ child: ConstrainedBox(
+ constraints:
+ const BoxConstraints.tightFor(height: 800, width: 450),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 40.0,
+ horizontal: 40,
+ ),
+ child: Column(
+ children: [
+ Column(
+ children: [
+ kDebugMode
+ ? GestureDetector(
+ child: const Align(
+ alignment: Alignment.topRight,
+ child: Text("Lang"),
+ ),
+ onTap: () async {
+ final locale = await getLocale();
+ routeToPage(
+ context,
+ LanguageSelectorPage(
+ appSupportedLocales,
+ (locale) async {
+ await setLocale(locale);
+ App.setLocale(context, locale);
+ },
+ locale,
+ ),
+ ).then((value) {
+ setState(() {});
+ });
+ },
+ )
+ : const SizedBox(),
+ Image.asset(
+ "assets/sheild-front-gradient.png",
+ width: 200,
+ height: 200,
+ ),
+ const SizedBox(height: 12),
+ const Text(
+ "ente",
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ fontFamily: 'Montserrat',
+ fontSize: 42,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ "Authenticator",
+ style: Theme.of(context).textTheme.headlineMedium,
+ ),
+ const SizedBox(height: 32),
+ Text(
+ l10n.onBoardingBody,
+ textAlign: TextAlign.center,
+ style:
+ Theme.of(context).textTheme.titleLarge!.copyWith(
+ color: Colors.white38,
+ // color: Theme.of(context)
+ // .colorScheme
+ // .mutedTextColor,
),
- ).then((value) {
- setState(() {});
- });
- },
- )
- : const SizedBox(),
- Image.asset(
- "assets/sheild-front-gradient.png",
- width: 200,
- height: 200,
- ),
- const SizedBox(height: 12),
- const Text(
- "ente",
- style: TextStyle(
- fontWeight: FontWeight.bold,
- fontFamily: 'Montserrat',
- fontSize: 42,
),
- ),
- const SizedBox(height: 4),
- Text(
- "Authenticator",
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- const SizedBox(height: 32),
- Text(
- l10n.onBoardingBody,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.titleLarge!.copyWith(
- color: Colors.white38,
+ ],
+ ),
+ const SizedBox(height: 100),
+ // TODO: Remove After Stable
+ // Container(
+ // width: double.infinity,
+ // padding: const EdgeInsets.symmetric(horizontal: 20),
+ // child: GradientButton(
+ // onTap: _navigateToSignUpPage,
+ // text: l10n.newUser,
+ // ),
+ // ),
+ // const SizedBox(height: 24),
+ Container(
+ height: 56,
+ width: double.infinity,
+ padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
+ child: Hero(
+ tag: "log_in",
+ child: ElevatedButton(
+ style: Theme.of(context)
+ .colorScheme
+ .optionalActionButtonStyle,
+ onPressed: _navigateToSignInPage,
+ child: Text(
+ l10n.existingUser,
+ style: const TextStyle(
+ color: Colors.black, // same for both themes
),
- ),
- ],
- ),
- const SizedBox(height: 100),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.symmetric(horizontal: 20),
- child: GradientButton(
- onTap: _navigateToSignUpPage,
- text: l10n.newUser,
- ),
- ),
- const SizedBox(height: 4),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.fromLTRB(20, 12, 20, 0),
- child: Hero(
- tag: "log_in",
- child: ElevatedButton(
- style: Theme.of(context)
- .colorScheme
- .optionalActionButtonStyle,
- onPressed: _navigateToSignInPage,
- child: Text(
- l10n.existingUser,
- style: const TextStyle(
- color: Colors.black, // same for both themes
),
),
),
),
- ),
- const SizedBox(height: 4),
- Container(
- width: double.infinity,
- padding: const EdgeInsets.only(top: 20, bottom: 20),
- child: GestureDetector(
- onTap: _optForOfflineMode,
- child: Center(
- child: Text(
- l10n.useOffline,
- style: body.copyWith(
- color: Theme.of(context).colorScheme.mutedTextColor,
- ),
- ),
- ),
- ),
- ),
- ],
+ const SizedBox(height: 4),
+ // TODO: Remove After Stable
+ // Container(
+ // width: double.infinity,
+ // padding: const EdgeInsets.only(top: 20, bottom: 20),
+ // child: GestureDetector(
+ // onTap: _optForOfflineMode,
+ // child: Center(
+ // child: Text(
+ // l10n.useOffline,
+ // style: body.copyWith(
+ // color:
+ // Theme.of(context).colorScheme.mutedTextColor,
+ // ),
+ // ),
+ // ),
+ // ),
+ // ),
+ ],
+ ),
),
),
),
@@ -172,65 +178,69 @@ class _OnboardingPageState extends State {
);
}
- Future _optForOfflineMode() async {
- bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
- if (!canCheckBio) {
- showToast(
- context,
- "Sorry, biometric authentication is not supported on this device.",
- );
- return;
- }
- final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode();
- ButtonResult? result;
- if (!hasOptedBefore) {
- result = await showChoiceActionSheet(
- context,
- title: context.l10n.warning,
- body: context.l10n.offlineModeWarning,
- secondButtonLabel: context.l10n.cancel,
- firstButtonLabel: context.l10n.ok,
- );
- }
- if (hasOptedBefore || result?.action == ButtonAction.first) {
- await Configuration.instance.optForOfflineMode();
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return const HomePage();
- },
- ),
- );
- }
- }
+ // TODO: Remove After Stable
+ // Future _optForOfflineMode() async {
+ // final canContinue = Platform.isMacOS || Platform.isLinux
+ // ? true
+ // : await LocalAuthentication().canCheckBiometrics;
- void _navigateToSignUpPage() {
- Widget page;
- if (Configuration.instance.getEncryptedToken() == null) {
- page = const EmailEntryPage();
- } else {
- // No key
- if (Configuration.instance.getKeyAttributes() == null) {
- // Never had a key
- page = const PasswordEntryPage(
- mode: PasswordEntryMode.set,
- );
- } else if (Configuration.instance.getKey() == null) {
- // Yet to decrypt the key
- page = const PasswordReentryPage();
- } else {
- // All is well, user just has not subscribed
- page = const HomePage();
- }
- }
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return page;
- },
- ),
- );
- }
+ // if (!canContinue) {
+ // showToast(
+ // context,
+ // "Sorry, biometric authentication is not supported on this device.",
+ // );
+ // return;
+ // }
+ // final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode();
+ // ButtonResult? result;
+ // if (!hasOptedBefore) {
+ // result = await showChoiceActionSheet(
+ // context,
+ // title: context.l10n.warning,
+ // body: context.l10n.offlineModeWarning,
+ // secondButtonLabel: context.l10n.cancel,
+ // firstButtonLabel: context.l10n.ok,
+ // );
+ // }
+ // if (hasOptedBefore || result?.action == ButtonAction.first) {
+ // await Configuration.instance.optForOfflineMode();
+ // Navigator.of(context).push(
+ // MaterialPageRoute(
+ // builder: (BuildContext context) {
+ // return const HomePage();
+ // },
+ // ),
+ // );
+ // }
+ // }
+
+ // void _navigateToSignUpPage() {
+ // Widget page;
+ // if (Configuration.instance.getEncryptedToken() == null) {
+ // page = const EmailEntryPage();
+ // } else {
+ // // No key
+ // if (Configuration.instance.getKeyAttributes() == null) {
+ // // Never had a key
+ // page = const PasswordEntryPage(
+ // mode: PasswordEntryMode.set,
+ // );
+ // } else if (Configuration.instance.getKey() == null) {
+ // // Yet to decrypt the key
+ // page = const PasswordReentryPage();
+ // } else {
+ // // All is well, user just has not subscribed
+ // page = const HomePage();
+ // }
+ // }
+ // Navigator.of(context).push(
+ // MaterialPageRoute(
+ // builder: (BuildContext context) {
+ // return page;
+ // },
+ // ),
+ // );
+ // }
void _navigateToSignInPage() {
Widget page;
diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
index ee46d7953..3937142d6 100644
--- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
+++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart
@@ -9,7 +9,7 @@ import "package:flutter/material.dart";
class SetupEnterSecretKeyPage extends StatefulWidget {
final Code? code;
- SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
+ SetupEnterSecretKeyPage({this.code, super.key});
@override
State createState() =>
@@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State {
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
);
_secretController = TextEditingController(
- text: widget.code != null ? widget.code!.secret : null,
+ text: widget.code?.secret,
);
_secretKeyObscured = widget.code != null;
super.initState();
@@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State {
appBar: AppBar(
title: Text(l10n.importAccountPageTitle),
),
- body: SafeArea(
- child: Center(
+ body: Center(
+ child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
diff --git a/auth/lib/onboarding/view/view_qr_page.dart b/auth/lib/onboarding/view/view_qr_page.dart
index 71f4756a7..687f810e3 100644
--- a/auth/lib/onboarding/view/view_qr_page.dart
+++ b/auth/lib/onboarding/view/view_qr_page.dart
@@ -1,4 +1,3 @@
-
import 'dart:math';
import "package:ente_auth/l10n/l10n.dart";
@@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
class ViewQrPage extends StatelessWidget {
final Code? code;
- ViewQrPage({this.code, Key? key}) : super(key: key);
+ ViewQrPage({this.code, super.key});
@override
Widget build(BuildContext context) {
@@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
appBar: AppBar(
title: Text(l10n.qrCode),
),
- body: SafeArea(
- child: Center(
+ body: Center(
+ child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
child: Column(
children: [
- QrImage(
+ QrImageView(
data: code!.rawData,
- foregroundColor: Theme.of(context).colorScheme.onBackground,
+ eyeStyle: QrEyeStyle(
+ eyeShape: QrEyeShape.square,
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
+ dataModuleStyle: QrDataModuleStyle(
+ dataModuleShape: QrDataModuleShape.square,
+ color: Theme.of(context).colorScheme.onBackground,
+ ),
version: QrVersions.auto,
size: qrSize,
),
diff --git a/auth/lib/services/authenticator_service.dart b/auth/lib/services/authenticator_service.dart
index cdc9a4fb3..4cb424440 100644
--- a/auth/lib/services/authenticator_service.dart
+++ b/auth/lib/services/authenticator_service.dart
@@ -16,9 +16,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
import 'package:ente_auth/store/authenticator_db.dart';
import 'package:ente_auth/store/offline_authenticator_db.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/foundation.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -26,6 +25,7 @@ enum AccountMode {
online,
offline,
}
+
extension on AccountMode {
bool get isOnline => this == AccountMode.online;
bool get isOffline => this == AccountMode.offline;
@@ -75,10 +75,10 @@ class AuthenticatorService {
final key = await getOrCreateAuthDataKey(mode);
for (LocalAuthEntity e in result) {
try {
- final decryptedValue = await CryptoUtil.decryptChaCha(
- Sodium.base642bin(e.encryptedData),
+ final decryptedValue = await CryptoUtil.decryptData(
+ CryptoUtil.base642bin(e.encryptedData),
key,
- Sodium.base642bin(e.header),
+ CryptoUtil.base642bin(e.header),
);
final hasSynced = !(e.id == null || e.shouldSync);
entities.add(
@@ -101,12 +101,13 @@ class AuthenticatorService {
AccountMode accountMode,
) async {
var key = await getOrCreateAuthDataKey(accountMode);
- final encryptedKeyData = await CryptoUtil.encryptChaCha(
- utf8.encode(plainText) as Uint8List,
+ final encryptedKeyData = await CryptoUtil.encryptData(
+ utf8.encode(plainText),
key,
);
- String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
- String header = Sodium.bin2base64(encryptedKeyData.header!);
+ String encryptedData =
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
+ String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
final insertedID = accountMode.isOnline
? await _db.insert(encryptedData, header)
: await _offlineDb.insert(encryptedData, header);
@@ -123,12 +124,13 @@ class AuthenticatorService {
AccountMode accountMode,
) async {
var key = await getOrCreateAuthDataKey(accountMode);
- final encryptedKeyData = await CryptoUtil.encryptChaCha(
- utf8.encode(plainText) as Uint8List,
+ final encryptedKeyData = await CryptoUtil.encryptData(
+ utf8.encode(plainText),
key,
);
- String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
- String header = Sodium.bin2base64(encryptedKeyData.header!);
+ String encryptedData =
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
+ String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
final int affectedRows = accountMode.isOnline
? await _db.updateEntry(generatedID, encryptedData, header)
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
@@ -154,7 +156,7 @@ class AuthenticatorService {
} else {
debugPrint("Skipping delete since account mode is offline");
}
- if(accountMode.isOnline) {
+ if (accountMode.isOnline) {
await _db.deleteByIDs(generatedIDs: [genID]);
} else {
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
@@ -163,7 +165,7 @@ class AuthenticatorService {
Future onlineSync() async {
try {
- if(getAccountMode().isOffline) {
+ if (getAccountMode().isOffline) {
debugPrint("Skipping sync since account mode is offline");
return false;
}
@@ -191,25 +193,25 @@ class AuthenticatorService {
Future _remoteToLocalSync() async {
_logger.info('Initiating remote to local sync');
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
- _logger.info("Current sync is " + lastSyncTime.toString());
+ _logger.info("Current sync is $lastSyncTime");
const int fetchLimit = 500;
final List result =
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
- _logger.info(result.length.toString() + " entries fetched from remote");
+ _logger.info("${result.length} entries fetched from remote");
if (result.isEmpty) {
return;
}
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
List deletedIDs =
result.where((element) => element.isDeleted).map((e) => e.id).toList();
- _logger.info(deletedIDs.length.toString() + " entries deleted");
+ _logger.info("${deletedIDs.length} entries deleted");
result.removeWhere((element) => element.isDeleted);
await _db.insertOrReplace(result);
if (deletedIDs.isNotEmpty) {
await _db.deleteByIDs(ids: deletedIDs);
}
_prefs.setInt(_lastEntitySyncTime, maxSyncTime);
- _logger.info("Setting synctime to " + maxSyncTime.toString());
+ _logger.info("Setting synctime to $maxSyncTime");
if (result.length == fetchLimit) {
_logger.info("Diff limit reached, pulling again");
await _remoteToLocalSync();
@@ -223,7 +225,7 @@ class AuthenticatorService {
.where((element) => element.shouldSync || element.id == null)
.toList();
_logger.info(
- pendingUpdate.length.toString() + " entries to be updated at remote",
+ "${pendingUpdate.length} entries to be updated at remote",
);
for (LocalAuthEntity entity in pendingUpdate) {
if (entity.id == null) {
@@ -253,7 +255,7 @@ class AuthenticatorService {
}
Future getOrCreateAuthDataKey(AccountMode mode) async {
- if(mode.isOffline) {
+ if (mode.isOffline) {
return _config.getOfflineSecretKey()!;
}
if (_config.getAuthSecretKey() != null) {
@@ -262,21 +264,21 @@ class AuthenticatorService {
try {
final AuthKey response = await _gateway.getKey();
final authKey = CryptoUtil.decryptSync(
- Sodium.base642bin(response.encryptedKey),
+ CryptoUtil.base642bin(response.encryptedKey),
_config.getKey()!,
- Sodium.base642bin(response.header),
+ CryptoUtil.base642bin(response.header),
);
- await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
+ await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
return authKey;
} on AuthenticatorKeyNotFound catch (e) {
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
final key = CryptoUtil.generateKey();
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
await _gateway.createKey(
- Sodium.bin2base64(encryptedKeyData.encryptedData!),
- Sodium.bin2base64(encryptedKeyData.nonce!),
+ CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
+ CryptoUtil.bin2base64(encryptedKeyData.nonce!),
);
- await _config.setAuthSecretKey(Sodium.bin2base64(key));
+ await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
return key;
} catch (e, s) {
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
diff --git a/auth/lib/services/billing_service.dart b/auth/lib/services/billing_service.dart
index 75a4e5579..926a11e4d 100644
--- a/auth/lib/services/billing_service.dart
+++ b/auth/lib/services/billing_service.dart
@@ -28,6 +28,8 @@ class BillingService {
final _dio = Network.instance.getDio();
final _config = Configuration.instance;
+ bool _isOnSubscriptionPage = false;
+
Subscription? _cachedSubscription;
Future? _future;
@@ -50,7 +52,7 @@ class BillingService {
Future> _fetchPrivateBillingPlans() {
return _dio.get(
- _config.getHttpEndpoint() + "/billing/user-plans/",
+ "${_config.getHttpEndpoint()}/billing/user-plans/",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -60,7 +62,7 @@ class BillingService {
}
Future> _fetchPublicBillingPlans() {
- return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
+ return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
}
Future verifySubscription(
@@ -70,7 +72,7 @@ class BillingService {
}) async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/verify-subscription",
+ "${_config.getHttpEndpoint()}/billing/verify-subscription",
data: {
"paymentProvider": paymentProvider ??
(Platform.isAndroid ? "playstore" : "appstore"),
@@ -84,7 +86,7 @@ class BillingService {
),
);
return Subscription.fromMap(response.data["subscription"]);
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && e.response!.statusCode == 409) {
throw SubscriptionAlreadyClaimedError();
} else {
@@ -100,7 +102,7 @@ class BillingService {
if (_cachedSubscription == null) {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/billing/subscription",
+ "${_config.getHttpEndpoint()}/billing/subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -109,7 +111,7 @@ class BillingService {
);
_cachedSubscription =
Subscription.fromMap(response.data["subscription"]);
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -120,7 +122,7 @@ class BillingService {
Future cancelStripeSubscription() async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
+ "${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -129,7 +131,7 @@ class BillingService {
);
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -138,7 +140,7 @@ class BillingService {
Future activateStripeSubscription() async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
+ "${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
options: Options(
headers: {
"X-Auth-Token": _config.getToken(),
@@ -147,7 +149,7 @@ class BillingService {
);
final subscription = Subscription.fromMap(response.data["subscription"]);
return subscription;
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
@@ -158,7 +160,7 @@ class BillingService {
}) async {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/billing/stripe/customer-portal",
+ "${_config.getHttpEndpoint()}/billing/stripe/customer-portal",
queryParameters: {
"redirectURL": kWebPaymentRedirectUrl,
},
@@ -169,9 +171,13 @@ class BillingService {
),
);
return response.data["url"];
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
_logger.severe(e, s);
rethrow;
}
}
+
+ void setIsOnSubscriptionPage(bool isOnSubscriptionPage) {
+ _isOnSubscriptionPage = isOnSubscriptionPage;
+ }
}
diff --git a/auth/lib/services/local_authentication_service.dart b/auth/lib/services/local_authentication_service.dart
index d21d1bcb2..6b6889429 100644
--- a/auth/lib/services/local_authentication_service.dart
+++ b/auth/lib/services/local_authentication_service.dart
@@ -1,15 +1,21 @@
+import 'dart:io';
+
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/ui/tools/app_lock.dart';
import 'package:ente_auth/utils/auth_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_local_authentication/flutter_local_authentication.dart';
import 'package:local_auth/local_auth.dart';
+import 'package:logging/logging.dart';
class LocalAuthenticationService {
LocalAuthenticationService._privateConstructor();
static final LocalAuthenticationService instance =
LocalAuthenticationService._privateConstructor();
+ final logger = Logger((LocalAuthenticationService).toString());
Future requestLocalAuthentication(
BuildContext context,
@@ -38,7 +44,7 @@ class LocalAuthenticationService {
String errorDialogContent, [
String errorDialogTitle = "",
]) async {
- if (await LocalAuthentication().isDeviceSupported()) {
+ if (await _isLocalAuthSupportedOnDevice()) {
AppLock.of(context)!.disable();
final result = await requestAuthentication(
context,
@@ -64,6 +70,12 @@ class LocalAuthenticationService {
}
Future _isLocalAuthSupportedOnDevice() async {
- return await LocalAuthentication().isDeviceSupported();
+ try {
+ return Platform.isMacOS || Platform.isLinux
+ ? await FlutterLocalAuthentication().canAuthenticate()
+ : await LocalAuthentication().isDeviceSupported();
+ } on MissingPluginException {
+ return false;
+ }
}
}
diff --git a/auth/lib/services/notification_service.dart b/auth/lib/services/notification_service.dart
index 009cce846..ce4bc536f 100644
--- a/auth/lib/services/notification_service.dart
+++ b/auth/lib/services/notification_service.dart
@@ -27,7 +27,7 @@ class NotificationService {
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>();
if (implementation != null) {
- implementation.requestPermission();
+ implementation.requestNotificationsPermission();
}
}
diff --git a/auth/lib/services/user_remote_flag_service.dart b/auth/lib/services/user_remote_flag_service.dart
index 6961518aa..74deae584 100644
--- a/auth/lib/services/user_remote_flag_service.dart
+++ b/auth/lib/services/user_remote_flag_service.dart
@@ -96,7 +96,7 @@ class UserRemoteFlagService {
queryParams["defaultValue"] = defaultValue;
}
final response = await _dio.get(
- _config.getHttpEndpoint() + "/remote-store",
+ "${_config.getHttpEndpoint()}/remote-store",
queryParameters: queryParams,
options: Options(
headers: {
@@ -119,7 +119,7 @@ class UserRemoteFlagService {
Future _updateKeyValue(String key, String value) async {
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/remote-store/update",
+ "${_config.getHttpEndpoint()}/remote-store/update",
data: {
"key": key,
"value": value,
diff --git a/auth/lib/services/user_service.dart b/auth/lib/services/user_service.dart
index 682412744..bc043d20d 100644
--- a/auth/lib/services/user_service.dart
+++ b/auth/lib/services/user_service.dart
@@ -28,9 +28,9 @@ import 'package:ente_auth/ui/common/progress_dialog.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/ui/two_factor_authentication_page.dart';
import 'package:ente_auth/ui/two_factor_recovery_page.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -78,7 +78,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/ott",
+ "${_config.getHttpEndpoint()}/users/ott",
data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
);
await dialog.hide();
@@ -100,7 +100,7 @@ class UserService {
return;
}
unawaited(showGenericErrorDialog(context: context));
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.info(e);
if (e.response != null && e.response!.statusCode == 403) {
@@ -127,7 +127,7 @@ class UserService {
String type = "SubCancellation",
}) async {
await _dio.post(
- _config.getHttpEndpoint() + "/anonymous/feedback",
+ "${_config.getHttpEndpoint()}/anonymous/feedback",
data: {"feedback": feedback, "type": "type"},
);
}
@@ -171,7 +171,7 @@ class UserService {
try {
final response = await _enteDio.get("/users/sessions");
return Sessions.fromMap(response.data);
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
rethrow;
}
@@ -185,7 +185,7 @@ class UserService {
"token": token,
},
);
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
rethrow;
}
@@ -194,7 +194,7 @@ class UserService {
Future leaveFamilyPlan() async {
try {
await _enteDio.delete("/family/leave");
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.warning('failed to leave family plan', e);
rethrow;
}
@@ -276,11 +276,11 @@ class UserService {
"ott": ott,
};
if (!_config.isLoggedIn()) {
- verifyData["source"] = 'auth:' + _getRefSource();
+ verifyData["source"] = 'auth:${_getRefSource()}';
}
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/verify-email",
+ "${_config.getHttpEndpoint()}/users/verify-email",
data: verifyData,
);
await dialog.hide();
@@ -315,7 +315,7 @@ class UserService {
// should never reach here
throw Exception("unexpected response during email verification");
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.info(e);
await dialog.hide();
if (e.response != null && e.response!.statusCode == 410) {
@@ -376,7 +376,7 @@ class UserService {
context.l10n.oops,
context.l10n.verificationFailedPleaseTryAgain,
);
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 403) {
showErrorDialog(
@@ -423,7 +423,7 @@ class UserService {
Future getSrpAttributes(String email) async {
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/users/srp/attributes",
+ "${_config.getHttpEndpoint()}/users/srp/attributes",
queryParameters: {
"email": email,
},
@@ -433,7 +433,7 @@ class UserService {
} else {
throw Exception("get-srp-attributes action failed");
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
if (e.response != null && e.response!.statusCode == 404) {
throw SrpSetupNotCompleteError();
}
@@ -486,10 +486,9 @@ class UserService {
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
- // ignore: unused_local_variable
- late Response srpCompleteResponse;
+ late Response _;
if (setKeysRequest == null) {
- srpCompleteResponse = await _enteDio.post(
+ _ = await _enteDio.post(
"/users/srp/complete",
data: {
'setupID': setupSRPResponse.setupID,
@@ -497,7 +496,7 @@ class UserService {
},
);
} else {
- srpCompleteResponse = await _enteDio.post(
+ _ = await _enteDio.post(
"/users/srp/update",
data: {
'setupID': setupSRPResponse.setupID,
@@ -536,7 +535,7 @@ class UserService {
late Uint8List keyEncryptionKey;
_logger.finest('Start deriving key');
keyEncryptionKey = await CryptoUtil.deriveKey(
- utf8.encode(userPassword) as Uint8List,
+ utf8.encode(userPassword),
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
@@ -559,7 +558,7 @@ class UserService {
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
- _config.getHttpEndpoint() + "/users/srp/create-session",
+ "${_config.getHttpEndpoint()}/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
@@ -573,7 +572,7 @@ class UserService {
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/srp/verify-session",
+ "${_config.getHttpEndpoint()}/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
@@ -667,7 +666,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/two-factor/verify",
+ "${_config.getHttpEndpoint()}/users/two-factor/verify",
data: {
"sessionID": sessionID,
"code": code,
@@ -686,7 +685,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
await dialog.hide();
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
@@ -722,7 +721,7 @@ class UserService {
await dialog.show();
try {
final response = await _dio.get(
- _config.getHttpEndpoint() + "/users/two-factor/recover",
+ "${_config.getHttpEndpoint()}/users/two-factor/recover",
queryParameters: {
"sessionID": sessionID,
},
@@ -741,7 +740,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, context.l10n.sessionExpired);
@@ -809,7 +808,7 @@ class UserService {
}
try {
final response = await _dio.post(
- _config.getHttpEndpoint() + "/users/two-factor/remove",
+ "${_config.getHttpEndpoint()}/users/two-factor/remove",
data: {
"sessionID": sessionID,
"secret": secret,
@@ -830,7 +829,7 @@ class UserService {
(route) => route.isFirst,
);
}
- } on DioError catch (e) {
+ } on DioException catch (e) {
_logger.severe(e);
if (e.response != null && e.response!.statusCode == 404) {
showToast(context, "Session expired");
diff --git a/auth/lib/store/authenticator_db.dart b/auth/lib/store/authenticator_db.dart
index 0f05e83c0..deb57bc81 100644
--- a/auth/lib/store/authenticator_db.dart
+++ b/auth/lib/store/authenticator_db.dart
@@ -3,10 +3,12 @@ import 'dart:io';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
+import 'package:ente_auth/utils/directory_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class AuthenticatorDB {
static const _databaseName = "ente.authenticator.db";
@@ -25,6 +27,16 @@ class AuthenticatorDB {
}
Future _initDatabase() async {
+ if (Platform.isWindows || Platform.isLinux) {
+ var databaseFactory = databaseFactoryFfi;
+ return await databaseFactory.openDatabase(
+ await DirectoryUtils.getDatabasePath(_databaseName),
+ options: OpenDatabaseOptions(
+ version: _databaseVersion,
+ onCreate: _onCreate,
+ ),
+ );
+ }
final Directory documentsDirectory =
await getApplicationDocumentsDirectory();
final String path = join(documentsDirectory.path, _databaseName);
@@ -166,7 +178,7 @@ class AuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]);
}
}
- await batch.commit();
+ final _ = await batch.commit();
debugPrint("Done");
}
diff --git a/auth/lib/store/offline_authenticator_db.dart b/auth/lib/store/offline_authenticator_db.dart
index ed8f93fb9..d1af51fff 100644
--- a/auth/lib/store/offline_authenticator_db.dart
+++ b/auth/lib/store/offline_authenticator_db.dart
@@ -3,10 +3,11 @@ import 'dart:io';
import 'package:ente_auth/models/authenticator/auth_entity.dart';
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
+import 'package:ente_auth/utils/directory_utils.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
-import 'package:sqflite/sqflite.dart';
+import 'package:sqflite_common_ffi/sqflite_ffi.dart';
class OfflineAuthenticatorDB {
static const _databaseName = "ente.offline_authenticator.db";
@@ -15,7 +16,8 @@ class OfflineAuthenticatorDB {
static const entityTable = 'entities';
OfflineAuthenticatorDB._privateConstructor();
- static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor();
+ static final OfflineAuthenticatorDB instance =
+ OfflineAuthenticatorDB._privateConstructor();
static Future? _dbFuture;
@@ -25,8 +27,18 @@ class OfflineAuthenticatorDB {
}
Future _initDatabase() async {
+ if (Platform.isWindows || Platform.isLinux) {
+ var databaseFactory = databaseFactoryFfi;
+ return await databaseFactory.openDatabase(
+ await DirectoryUtils.getDatabasePath(_databaseName),
+ options: OpenDatabaseOptions(
+ version: _databaseVersion,
+ onCreate: _onCreate,
+ ),
+ );
+ }
final Directory documentsDirectory =
- await getApplicationDocumentsDirectory();
+ await getApplicationDocumentsDirectory();
final String path = join(documentsDirectory.path, _databaseName);
debugPrint(path);
return await openDatabase(
@@ -70,10 +82,10 @@ class OfflineAuthenticatorDB {
}
Future updateEntry(
- int generatedID,
- String encData,
- String header,
- ) async {
+ int generatedID,
+ String encData,
+ String header,
+ ) async {
final db = await instance.database;
final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch;
int affectedRows = await db.update(
@@ -151,7 +163,7 @@ class OfflineAuthenticatorDB {
batch.delete(entityTable, where: whereID, whereArgs: [id]);
}
}
- await batch.commit();
+ final _ = await batch.commit();
debugPrint("Done");
}
diff --git a/auth/lib/store/user_store.dart b/auth/lib/store/user_store.dart
index 20f3ead72..30409671b 100644
--- a/auth/lib/store/user_store.dart
+++ b/auth/lib/store/user_store.dart
@@ -3,7 +3,6 @@ import 'package:shared_preferences/shared_preferences.dart';
class UserStore {
UserStore._privateConstructor();
- // ignore: unused_field
late SharedPreferences _preferences;
static final UserStore instance = UserStore._privateConstructor();
diff --git a/auth/lib/theme/colors.dart b/auth/lib/theme/colors.dart
index 1d4a8517c..9ac9d2d7e 100644
--- a/auth/lib/theme/colors.dart
+++ b/auth/lib/theme/colors.dart
@@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
const Color _warning800 = Color(0xFFF53434);
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
+// ignore: unused_element
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
diff --git a/auth/lib/ui/account/change_email_dialog.dart b/auth/lib/ui/account/change_email_dialog.dart
index 7cfa10f4a..828970cd7 100644
--- a/auth/lib/ui/account/change_email_dialog.dart
+++ b/auth/lib/ui/account/change_email_dialog.dart
@@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
import 'package:flutter/material.dart';
class ChangeEmailDialog extends StatefulWidget {
- const ChangeEmailDialog({Key? key}) : super(key: key);
+ const ChangeEmailDialog({super.key});
@override
State createState() => _ChangeEmailDialogState();
diff --git a/auth/lib/ui/account/delete_account_page.dart b/auth/lib/ui/account/delete_account_page.dart
index e4376c982..b6a314ec7 100644
--- a/auth/lib/ui/account/delete_account_page.dart
+++ b/auth/lib/ui/account/delete_account_page.dart
@@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/common/dialogs.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/email_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class DeleteAccountPage extends StatelessWidget {
const DeleteAccountPage({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
l10n.initiateAccountDeleteTitle,
);
+ await PlatformUtil.refocusWindows();
+
if (hasAuthenticated) {
final choice = await showChoiceDialogOld(
context,
@@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
return;
}
final decryptChallenge = CryptoUtil.openSealSync(
- Sodium.base642bin(response.encryptedChallenge),
- Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
+ CryptoUtil.base642bin(response.encryptedChallenge),
+ CryptoUtil.base642bin(
+ Configuration.instance.getKeyAttributes()!.publicKey,
+ ),
Configuration.instance.getSecretKey()!,
);
final challengeResponseStr = utf8.decode(decryptChallenge);
diff --git a/auth/lib/ui/account/email_entry_page.dart b/auth/lib/ui/account/email_entry_page.dart
index a20f6e245..f932535f7 100644
--- a/auth/lib/ui/account/email_entry_page.dart
+++ b/auth/lib/ui/account/email_entry_page.dart
@@ -5,7 +5,7 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -14,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart';
import "package:styled_text/styled_text.dart";
class EmailEntryPage extends StatefulWidget {
- const EmailEntryPage({Key? key}) : super(key: key);
+ const EmailEntryPage({super.key});
@override
State createState() => _EmailEntryPageState();
@@ -427,15 +427,10 @@ class _EmailEntryPageState extends State {
tags: {
'u-terms': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.termsOfServicesTitle,
- "https://ente.io/terms",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.termsOfServicesTitle,
+ "https://ente.io/terms",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -443,15 +438,10 @@ class _EmailEntryPageState extends State {
),
'u-policy': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.privacyPolicyTitle,
- "https://ente.io/privacy",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.privacyPolicyTitle,
+ "https://ente.io/privacy",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -494,15 +484,10 @@ class _EmailEntryPageState extends State {
tags: {
'underline': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.encryption,
- "https://ente.io/architecture",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.encryption,
+ "https://ente.io/architecture",
),
style: const TextStyle(
decoration: TextDecoration.underline,
diff --git a/auth/lib/ui/account/login_page.dart b/auth/lib/ui/account/login_page.dart
index bfa50b8c2..a1c3fe727 100644
--- a/auth/lib/ui/account/login_page.dart
+++ b/auth/lib/ui/account/login_page.dart
@@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import "package:styled_text/styled_text.dart";
class LoginPage extends StatefulWidget {
- const LoginPage({Key? key}) : super(key: key);
+ const LoginPage({super.key});
@override
State createState() => _LoginPageState();
@@ -25,6 +25,36 @@ class _LoginPageState extends State {
Color? _emailInputFieldColor;
final Logger _logger = Logger('_LoginPageState');
+ Future onPressed() async {
+ UserService.instance.setEmail(_email!);
+ Configuration.instance.resetVolatilePassword();
+ SrpAttributes? attr;
+ bool isEmailVerificationEnabled = true;
+ try {
+ attr = await UserService.instance.getSrpAttributes(_email!);
+ isEmailVerificationEnabled = attr.isEmailMFAEnabled;
+ } catch (e) {
+ if (e is! SrpSetupNotCompleteError) {
+ _logger.severe('Error getting SRP attributes', e);
+ }
+ }
+ if (attr != null && !isEmailVerificationEnabled) {
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (BuildContext context) {
+ return LoginPasswordVerificationPage(
+ srpAttributes: attr!,
+ );
+ },
+ ),
+ );
+ } else {
+ await UserService.instance
+ .sendOtt(context, _email!, isCreateAccountScreen: false);
+ }
+ FocusScope.of(context).unfocus();
+ }
+
@override
void initState() {
_email = _config.getEmail();
@@ -60,35 +90,7 @@ class _LoginPageState extends State {
isKeypadOpen: isKeypadOpen,
isFormValid: _emailIsValid,
buttonText: context.l10n.logInLabel,
- onPressedFunction: () async {
- UserService.instance.setEmail(_email!);
- Configuration.instance.resetVolatilePassword();
- SrpAttributes? attr;
- bool isEmailVerificationEnabled = true;
- try {
- attr = await UserService.instance.getSrpAttributes(_email!);
- isEmailVerificationEnabled = attr.isEmailMFAEnabled;
- } catch (e) {
- if (e is! SrpSetupNotCompleteError) {
- _logger.severe('Error getting SRP attributes', e);
- }
- }
- if (attr != null && !isEmailVerificationEnabled) {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return LoginPasswordVerificationPage(
- srpAttributes: attr!,
- );
- },
- ),
- );
- } else {
- await UserService.instance
- .sendOtt(context, _email!, isCreateAccountScreen: false);
- }
- FocusScope.of(context).unfocus();
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -115,6 +117,8 @@ class _LoginPageState extends State {
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField(
autofillHints: const [AutofillHints.email],
+ onFieldSubmitted:
+ _emailIsValid ? (value) => onPressed() : null,
decoration: InputDecoration(
fillColor: _emailInputFieldColor,
filled: true,
@@ -178,15 +182,10 @@ class _LoginPageState extends State {
tags: {
'u-terms': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.termsOfServicesTitle,
- "https://ente.io/terms",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.termsOfServicesTitle,
+ "https://ente.io/terms",
),
style: const TextStyle(
decoration: TextDecoration.underline,
@@ -194,15 +193,10 @@ class _LoginPageState extends State {
),
'u-policy': StyledTextActionTag(
(String? text, Map attrs) =>
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.privacyPolicyTitle,
- "https://ente.io/privacy",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.privacyPolicyTitle,
+ "https://ente.io/privacy",
),
style: const TextStyle(
decoration: TextDecoration.underline,
diff --git a/auth/lib/ui/account/login_pwd_verification_page.dart b/auth/lib/ui/account/login_pwd_verification_page.dart
index 3bd39a1b7..2d2754ec1 100644
--- a/auth/lib/ui/account/login_pwd_verification_page.dart
+++ b/auth/lib/ui/account/login_pwd_verification_page.dart
@@ -1,6 +1,5 @@
import "package:dio/dio.dart";
import 'package:ente_auth/core/configuration.dart';
-import "package:ente_auth/core/errors.dart";
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/models/api/user/srp.dart";
import "package:ente_auth/services/user_service.dart";
@@ -9,6 +8,7 @@ import 'package:ente_auth/ui/common/dynamic_fab.dart';
import "package:ente_auth/ui/components/buttons/button_widget.dart";
import "package:ente_auth/utils/dialog_util.dart";
import "package:ente_auth/utils/email_util.dart";
+import "package:ente_crypto_dart/ente_crypto_dart.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
@@ -19,8 +19,7 @@ import "package:logging/logging.dart";
// volatile password.
class LoginPasswordVerificationPage extends StatefulWidget {
final SrpAttributes srpAttributes;
- const LoginPasswordVerificationPage({Key? key, required this.srpAttributes})
- : super(key: key);
+ const LoginPasswordVerificationPage({super.key, required this.srpAttributes});
@override
State createState() =>
@@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
bool _passwordInFocus = false;
bool _passwordVisible = false;
+ Future onPressed() async {
+ FocusScope.of(context).unfocus();
+ await verifyPassword(context, _passwordController.text);
+ }
+
@override
void initState() {
super.initState();
@@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState
isKeypadOpen: isKeypadOpen,
isFormValid: _passwordController.text.isNotEmpty,
buttonText: context.l10n.logInLabel,
- onPressedFunction: () async {
- FocusScope.of(context).unfocus();
- await verifyPassword(context, _passwordController.text);
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState
password,
dialog,
);
- } on DioError catch (e, s) {
+ } on DioException catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
@@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState
);
} else {
_logger.severe('API failure during SRP login', e, s);
- if (e.type == DioErrorType.other) {
+ if (e.type == DioExceptionType.unknown) {
await _showContactSupportDialog(
context,
context.l10n.noInternetConnection,
@@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState
Padding(
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
child: TextFormField(
+ onFieldSubmitted: _passwordController.text.isNotEmpty
+ ? (_) => onPressed()
+ : null,
key: const ValueKey("passwordInputField"),
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
diff --git a/auth/lib/ui/account/ott_verification_page.dart b/auth/lib/ui/account/ott_verification_page.dart
index 8bcaf3e34..cc9661def 100644
--- a/auth/lib/ui/account/ott_verification_page.dart
+++ b/auth/lib/ui/account/ott_verification_page.dart
@@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
this.isChangeEmail = false,
this.isCreateAccountScreen = false,
this.isResetPasswordScreen = false,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _OTTVerificationPageState();
@@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
class _OTTVerificationPageState extends State {
final _verificationCodeController = TextEditingController();
+ Future onPressed() async {
+ if (widget.isChangeEmail) {
+ await UserService.instance.changeEmail(
+ context,
+ widget.email,
+ _verificationCodeController.text,
+ );
+ } else {
+ await UserService.instance.verifyEmail(
+ context,
+ _verificationCodeController.text,
+ isResettingPasswordScreen: widget.isResetPasswordScreen,
+ );
+ }
+ FocusScope.of(context).unfocus();
+ }
+
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
@@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State {
body: _getBody(),
floatingActionButton: DynamicFAB(
isKeypadOpen: isKeypadOpen,
- isFormValid: !(_verificationCodeController.text.isEmpty),
+ isFormValid: _verificationCodeController.text.isNotEmpty,
buttonText: l10n.verify,
- onPressedFunction: () {
- if (widget.isChangeEmail) {
- UserService.instance.changeEmail(
- context,
- widget.email,
- _verificationCodeController.text,
- );
- } else {
- UserService.instance
- .verifyEmail(context, _verificationCodeController.text,
- isResettingPasswordScreen: widget.isResetPasswordScreen,);
- }
- FocusScope.of(context).unfocus();
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State {
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
child: TextFormField(
style: Theme.of(context).textTheme.titleMedium,
+ onFieldSubmitted: _verificationCodeController.text.isNotEmpty
+ ? (_) => onPressed()
+ : null,
decoration: InputDecoration(
filled: true,
hintText: l10n.tapToEnterCode,
diff --git a/auth/lib/ui/account/password_entry_page.dart b/auth/lib/ui/account/password_entry_page.dart
index db86b1186..e7714fb4a 100644
--- a/auth/lib/ui/account/password_entry_page.dart
+++ b/auth/lib/ui/account/password_entry_page.dart
@@ -4,11 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/account/recovery_key_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/home_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -25,7 +25,7 @@ enum PasswordEntryMode {
class PasswordEntryPage extends StatefulWidget {
final PasswordEntryMode mode;
- const PasswordEntryPage({required this.mode, Key? key}) : super(key: key);
+ const PasswordEntryPage({required this.mode, super.key});
@override
State createState() => _PasswordEntryPageState();
@@ -343,15 +343,10 @@ class _PasswordEntryPageState extends State {
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(
- context.l10n.howItWorks,
- "https://ente.io/architecture",
- );
- },
- ),
+ PlatformUtil.openWebView(
+ context,
+ context.l10n.howItWorks,
+ "https://ente.io/architecture",
);
},
child: Container(
diff --git a/auth/lib/ui/account/password_reentry_page.dart b/auth/lib/ui/account/password_reentry_page.dart
index a4570c5db..7a53e4a8d 100644
--- a/auth/lib/ui/account/password_reentry_page.dart
+++ b/auth/lib/ui/account/password_reentry_page.dart
@@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/home_page.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/email_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class PasswordReentryPage extends StatefulWidget {
- const PasswordReentryPage({Key? key}) : super(key: key);
+ const PasswordReentryPage({super.key});
@override
State createState() => _PasswordReentryPageState();
@@ -260,8 +260,8 @@ class _PasswordReentryPageState extends State {
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
- child: Wrap(
- alignment: WrapAlignment.spaceBetween,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
@@ -274,13 +274,17 @@ class _PasswordReentryPageState extends State {
),
);
},
- child: Text(
- context.l10n.forgotPassword,
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ child: Center(
+ child: Text(
+ context.l10n.forgotPassword,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
),
),
GestureDetector(
@@ -296,13 +300,17 @@ class _PasswordReentryPageState extends State {
Navigator.of(context)
.popUntil((route) => route.isFirst);
},
- child: Text(
- context.l10n.changeEmail,
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ child: Center(
+ child: Text(
+ context.l10n.changeEmail,
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
+ ),
),
),
],
diff --git a/auth/lib/ui/account/recovery_key_page.dart b/auth/lib/ui/account/recovery_key_page.dart
index c5ca66fdc..1b971b730 100644
--- a/auth/lib/ui/account/recovery_key_page.dart
+++ b/auth/lib/ui/account/recovery_key_page.dart
@@ -1,3 +1,4 @@
+import 'dart:convert';
import 'dart:io' as io;
import 'package:bip39/bip39.dart' as bip39;
@@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/common/gradient_button.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/share_utils.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:file_saver/file_saver.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
@@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
const RecoveryKeyPage(
this.recoveryKey,
this.doneText, {
- Key? key,
+ super.key,
this.showAppBar,
this.onDone,
this.isDismissible,
@@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
this.text,
this.subText,
this.showProgressBar = false,
- }) : super(key: key);
+ });
@override
State createState() => _RecoveryKeyPageState();
@@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
class _RecoveryKeyPageState extends State {
bool _hasTriedToSave = false;
final _recoveryKeyFile = io.File(
- Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
+ "${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
);
@override
@@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State {
? 32
: 120;
+ Future copy() async {
+ await Clipboard.setData(
+ ClipboardData(
+ text: recoveryKey,
+ ),
+ );
+ showShortToast(
+ context,
+ context.l10n.recoveryKeyCopiedToClipboard,
+ );
+ setState(() {
+ _hasTriedToSave = true;
+ });
+ }
+
return Scaffold(
appBar: widget.showProgressBar
? AppBar(
@@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State {
style: Theme.of(context).textTheme.titleMedium,
),
const Padding(padding: EdgeInsets.only(top: 24)),
- DottedBorder(
- color: const Color.fromARGB(255, 105, 17, 127),
- //color of dotted/dash line
- strokeWidth: 1,
- //thickness of dash/dots
- dashPattern: const [6, 6],
- radius: const Radius.circular(8),
- //dash patterns, 10 is dash width, 6 is space width
- child: SizedBox(
- //inner container
- // height: 120, //height of inner container
- width: double
- .infinity, //width to 100% match to parent container.
- // ignore: prefer_const_literals_to_create_immutables
- child: Column(
- children: [
- GestureDetector(
- onTap: () async {
- await Clipboard.setData(
- ClipboardData(text: recoveryKey),
- );
- showShortToast(
- context,
- context.l10n.recoveryKeyCopiedToClipboard,
- );
- setState(() {
- _hasTriedToSave = true;
- });
- },
- child: Container(
- decoration: BoxDecoration(
- border: Border.all(
- color: const Color.fromRGBO(
- 49,
- 155,
- 86,
- .2,
- ),
+ Container(
+ padding: const EdgeInsets.all(1),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ gradient: const LinearGradient(
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ colors: [
+ Color(0x8E9610D6),
+ Color(0x8E9F4FC6),
+ ],
+ stops: [0.0, 0.9753],
+ ),
+ ),
+ child: DottedBorder(
+ padding: EdgeInsets.zero,
+ borderType: BorderType.RRect,
+ strokeWidth: 1,
+ color: const Color(0xFF6B6B6B),
+ dashPattern: const [6, 6],
+ radius: const Radius.circular(8),
+ child: SizedBox(
+ width: double.infinity,
+ child: Stack(
+ children: [
+ Column(
+ children: [
+ Builder(
+ builder: (context) {
+ final content = Container(
+ padding: const EdgeInsets.all(20),
+ width: double.infinity,
+ child: Text(
+ recoveryKey,
+ textAlign: TextAlign.justify,
+ style: Theme.of(context)
+ .textTheme
+ .bodyLarge,
+ ),
+ );
+
+ if (PlatformUtil.isMobile()) {
+ return GestureDetector(
+ onTap: () async => await copy(),
+ child: content,
+ );
+ } else {
+ return SelectableRegion(
+ focusNode: FocusNode(),
+ selectionControls:
+ PlatformUtil.selectionControls,
+ child: content,
+ );
+ }
+ },
),
- borderRadius: const BorderRadius.all(
- Radius.circular(2),
- ),
- color: Theme.of(context)
- .colorScheme
- .recoveryKeyBoxColor,
- ),
- padding: const EdgeInsets.all(20),
- width: double.infinity,
- child: Text(
- recoveryKey,
- style:
- Theme.of(context).textTheme.bodyLarge,
+ ],
+ ),
+ Positioned(
+ right: 0,
+ top: 0,
+ child: PlatformCopy(
+ onPressed: copy,
),
),
- ),
- ],
+ ],
+ ),
),
),
),
@@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State {
),
),
],
- ), // columnEnds
+ ),
),
),
);
@@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State {
final List childrens = [];
if (!_hasTriedToSave) {
childrens.add(
- ElevatedButton(
- style: Theme.of(context).colorScheme.optionalActionButtonStyle,
- onPressed: () async {
- await _saveKeys();
- },
- child: Text(context.l10n.doThisLater),
+ SizedBox(
+ height: 56,
+ child: ElevatedButton(
+ style: Theme.of(context).colorScheme.optionalActionButtonStyle,
+ onPressed: () async {
+ await _saveKeys();
+ },
+ child: Text(context.l10n.doThisLater),
+ ),
),
);
childrens.add(const SizedBox(height: 10));
@@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State {
childrens.add(
GradientButton(
onTap: () async {
- await _shareRecoveryKey(recoveryKey);
+ shareDialog(
+ context,
+ context.l10n.recoveryKey,
+ saveAction: () async {
+ await _saveRecoveryKey(recoveryKey);
+ },
+ sendAction: () async {
+ await _shareRecoveryKey(recoveryKey);
+ },
+ );
},
text: context.l10n.saveKey,
),
);
+
if (_hasTriedToSave) {
childrens.add(const SizedBox(height: 10));
childrens.add(
- ElevatedButton(
- child: Text(widget.doneText),
- onPressed: () async {
- await _saveKeys();
- },
+ SizedBox(
+ height: 56,
+ child: ElevatedButton(
+ child: Text(widget.doneText),
+ onPressed: () async {
+ await _saveKeys();
+ },
+ ),
),
);
}
@@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State {
return childrens;
}
+ Future _saveRecoveryKey(String recoveryKey) async {
+ final bytes = utf8.encode(recoveryKey);
+ final time = DateTime.now().millisecondsSinceEpoch;
+
+ await PlatformUtil.shareFile(
+ "ente_recovery_key_$time",
+ "txt",
+ bytes,
+ MimeType.text,
+ );
+
+ if (mounted) {
+ showToast(
+ context,
+ context.l10n.recoveryKeySaved,
+ );
+ setState(() {
+ _hasTriedToSave = true;
+ });
+ }
+ }
+
Future _shareRecoveryKey(String recoveryKey) async {
if (_recoveryKeyFile.existsSync()) {
await _recoveryKeyFile.delete();
}
_recoveryKeyFile.writeAsStringSync(recoveryKey);
+ // ignore: deprecated_member_use
await Share.shareFiles([_recoveryKeyFile.path]);
Future.delayed(const Duration(milliseconds: 500), () {
if (mounted) {
@@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State {
widget.onDone!();
}
}
+
+class PlatformCopy extends StatelessWidget {
+ const PlatformCopy({
+ super.key,
+ required this.onPressed,
+ });
+
+ final void Function() onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return IconButton(
+ onPressed: () => onPressed(),
+ visualDensity: VisualDensity.compact,
+ icon: const Icon(
+ Icons.copy,
+ size: 16,
+ ),
+ );
+ }
+}
diff --git a/auth/lib/ui/account/recovery_page.dart b/auth/lib/ui/account/recovery_page.dart
index acfd01904..1d55fbb53 100644
--- a/auth/lib/ui/account/recovery_page.dart
+++ b/auth/lib/ui/account/recovery_page.dart
@@ -1,8 +1,7 @@
-
-
import 'dart:ui';
import 'package:ente_auth/core/configuration.dart';
+import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/common/dynamic_fab.dart';
import 'package:ente_auth/utils/dialog_util.dart';
@@ -10,7 +9,7 @@ import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class RecoveryPage extends StatefulWidget {
- const RecoveryPage({Key? key}) : super(key: key);
+ const RecoveryPage({super.key});
@override
State createState() => _RecoveryPageState();
@@ -19,6 +18,36 @@ class RecoveryPage extends StatefulWidget {
class _RecoveryPageState extends State {
final _recoveryKey = TextEditingController();
+ Future onPressed() async {
+ FocusScope.of(context).unfocus();
+ final dialog = createProgressDialog(context, "Decrypting...");
+ await dialog.show();
+ try {
+ await Configuration.instance.recover(_recoveryKey.text.trim());
+ await dialog.hide();
+ showToast(context, "Recovery successful!");
+ Navigator.of(context).pushReplacement(
+ MaterialPageRoute(
+ builder: (BuildContext context) {
+ return const PopScope(
+ canPop: false,
+ child: PasswordEntryPage(
+ mode: PasswordEntryMode.reset,
+ ),
+ );
+ },
+ ),
+ );
+ } catch (e) {
+ await dialog.hide();
+ String errMessage = 'The recovery key you entered is incorrect';
+ if (e is AssertionError) {
+ errMessage = '$errMessage : ${e.message}';
+ }
+ showErrorDialog(context, "Incorrect recovery key", errMessage);
+ }
+ }
+
@override
Widget build(BuildContext context) {
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
@@ -46,35 +75,7 @@ class _RecoveryPageState extends State {
isKeypadOpen: isKeypadOpen,
isFormValid: _recoveryKey.text.isNotEmpty,
buttonText: 'Recover',
- onPressedFunction: () async {
- FocusScope.of(context).unfocus();
- final dialog = createProgressDialog(context, "Decrypting...");
- await dialog.show();
- try {
- await Configuration.instance.recover(_recoveryKey.text.trim());
- await dialog.hide();
- showToast(context, "Recovery successful!");
- Navigator.of(context).pushReplacement(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WillPopScope(
- onWillPop: () async => false,
- child: const PasswordEntryPage(
- mode: PasswordEntryMode.reset,
- ),
- );
- },
- ),
- );
- } catch (e) {
- await dialog.hide();
- String errMessage = 'The recovery key you entered is incorrect';
- if (e is AssertionError) {
- errMessage = '$errMessage : ${e.message}';
- }
- showErrorDialog(context, "Incorrect recovery key", errMessage);
- }
- },
+ onPressedFunction: onPressed,
),
floatingActionButtonLocation: fabLocation(),
floatingActionButtonAnimator: NoScalingAnimation(),
@@ -87,7 +88,7 @@ class _RecoveryPageState extends State {
padding:
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
child: Text(
- 'Forgot password',
+ context.l10n.forgotPassword,
style: Theme.of(context).textTheme.headlineMedium,
),
),
@@ -139,11 +140,13 @@ class _RecoveryPageState extends State {
child: Center(
child: Text(
"No recovery key?",
- style:
- Theme.of(context).textTheme.titleMedium!.copyWith(
- fontSize: 14,
- decoration: TextDecoration.underline,
- ),
+ style: Theme.of(context)
+ .textTheme
+ .titleMedium!
+ .copyWith(
+ fontSize: 14,
+ decoration: TextDecoration.underline,
+ ),
),
),
),
diff --git a/auth/lib/ui/account/request_pwd_verification_page.dart b/auth/lib/ui/account/request_pwd_verification_page.dart
index 02be12347..0e557bb80 100644
--- a/auth/lib/ui/account/request_pwd_verification_page.dart
+++ b/auth/lib/ui/account/request_pwd_verification_page.dart
@@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
import "package:ente_auth/l10n/l10n.dart";
import "package:ente_auth/theme/ente_theme.dart";
import 'package:ente_auth/ui/common/dynamic_fab.dart';
-import "package:ente_auth/utils/crypto_util.dart";
import "package:ente_auth/utils/dialog_util.dart";
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import "package:flutter_sodium/flutter_sodium.dart";
import "package:logging/logging.dart";
typedef OnPasswordVerifiedFn = Future Function(Uint8List bytes);
@@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
final OnPasswordVerifiedFn onPasswordVerified;
final Function? onPasswordError;
- const RequestPasswordVerificationPage(
- {super.key, required this.onPasswordVerified, this.onPasswordError,});
+ const RequestPasswordVerificationPage({
+ super.key,
+ required this.onPasswordVerified,
+ this.onPasswordError,
+ });
@override
State createState() =>
@@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
try {
final attributes = Configuration.instance.getKeyAttributes()!;
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
- utf8.encode(_passwordController.text) as Uint8List,
- Sodium.base642bin(attributes.kekSalt),
+ utf8.encode(_passwordController.text),
+ CryptoUtil.base642bin(attributes.kekSalt),
attributes.memLimit,
attributes.opsLimit,
);
CryptoUtil.decryptSync(
- Sodium.base642bin(attributes.encryptedKey),
+ CryptoUtil.base642bin(attributes.encryptedKey),
keyEncryptionKey,
- Sodium.base642bin(attributes.keyDecryptionNonce),
+ CryptoUtil.base642bin(attributes.keyDecryptionNonce),
);
dialog.show();
// pop
diff --git a/auth/lib/ui/account/sessions_page.dart b/auth/lib/ui/account/sessions_page.dart
index 30fdc1ed0..d5e45a8e9 100644
--- a/auth/lib/ui/account/sessions_page.dart
+++ b/auth/lib/ui/account/sessions_page.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class SessionsPage extends StatefulWidget {
- const SessionsPage({Key? key}) : super(key: key);
+ const SessionsPage({super.key});
@override
State createState() => _SessionsPageState();
diff --git a/auth/lib/ui/account/verify_recovery_page.dart b/auth/lib/ui/account/verify_recovery_page.dart
index dad67590b..146f3ea92 100644
--- a/auth/lib/ui/account/verify_recovery_page.dart
+++ b/auth/lib/ui/account/verify_recovery_page.dart
@@ -12,12 +12,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
class VerifyRecoveryPage extends StatefulWidget {
- const VerifyRecoveryPage({Key? key}) : super(key: key);
+ const VerifyRecoveryPage({super.key});
@override
State createState() => _VerifyRecoveryPageState();
@@ -34,14 +35,14 @@ class _VerifyRecoveryPageState extends State {
try {
final String inputKey = _recoveryKey.text.trim();
final String recoveryKey =
- Sodium.bin2hex(Configuration.instance.getRecoveryKey());
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
try {
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
} catch (e) {
await dialog.hide();
- if (e is DioError && e.type == DioErrorType.other) {
+ if (e is DioException && e.type == DioExceptionType.unknown) {
await showErrorDialog(
context,
"No internet connection",
@@ -88,10 +89,13 @@ class _VerifyRecoveryPageState extends State {
context,
"Please authenticate to view your recovery key",
);
+ await PlatformUtil.refocusWindows();
+
if (hasAuthenticated) {
String recoveryKey;
try {
- recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
+ recoveryKey =
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
routeToPage(
context,
RecoveryKeyPage(
diff --git a/auth/lib/ui/code_timer_progress.dart b/auth/lib/ui/code_timer_progress.dart
index 1dbb4358e..b524a0c23 100644
--- a/auth/lib/ui/code_timer_progress.dart
+++ b/auth/lib/ui/code_timer_progress.dart
@@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
final int period;
CodeTimerProgress({
- Key? key,
+ super.key,
required this.period,
- }) : super(key: key);
+ });
@override
- _CodeTimerProgressState createState() => _CodeTimerProgressState();
+ State createState() => _CodeTimerProgressState();
}
class _CodeTimerProgressState extends State
diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart
index 6494df5cf..da656803f 100644
--- a/auth/lib/ui/code_widget.dart
+++ b/auth/lib/ui/code_widget.dart
@@ -14,6 +14,7 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/code_timer_progress.dart';
import 'package:ente_auth/ui/utils/icon_utils.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:ente_auth/utils/totp_util.dart';
import 'package:flutter/material.dart';
@@ -24,7 +25,7 @@ import 'package:move_to_background/move_to_background.dart';
class CodeWidget extends StatefulWidget {
final Code code;
- const CodeWidget(this.code, {Key? key}) : super(key: key);
+ const CodeWidget(this.code, {super.key});
@override
State createState() => _CodeWidgetState();
@@ -372,9 +373,10 @@ class _CodeWidgetState extends State {
}
Future _onEditPressed(_) async {
- bool _isAuthSuccessful = await LocalAuthenticationService.instance
+ bool isAuthSuccessful = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
- if (!_isAuthSuccessful) {
+ await PlatformUtil.refocusWindows();
+ if (!isAuthSuccessful) {
return;
}
final Code? code = await Navigator.of(context).push(
@@ -390,9 +392,10 @@ class _CodeWidgetState extends State {
}
Future _onShowQrPressed(_) async {
- bool _isAuthSuccessful = await LocalAuthenticationService.instance
+ bool isAuthSuccessful = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
- if (!_isAuthSuccessful) {
+ await PlatformUtil.refocusWindows();
+ if (!isAuthSuccessful) {
return;
}
// ignore: unused_local_variable
@@ -406,14 +409,15 @@ class _CodeWidgetState extends State {
}
void _onDeletePressed(_) async {
- bool _isAuthSuccessful =
+ bool isAuthSuccessful =
await LocalAuthenticationService.instance.requestLocalAuthentication(
context,
context.l10n.deleteCodeAuthMessage,
);
- if (!_isAuthSuccessful) {
+ if (!isAuthSuccessful) {
return;
}
+ FocusScope.of(context).requestFocus();
final l10n = context.l10n;
await showChoiceActionSheet(
context,
@@ -450,7 +454,7 @@ class _CodeWidgetState extends State {
code = code.replaceAll(RegExp(r'\d'), '•');
}
if (code.length == 6) {
- return code.substring(0, 3) + " " + code.substring(3, 6);
+ return "${code.substring(0, 3)} ${code.substring(3, 6)}";
}
return code;
}
diff --git a/auth/lib/ui/common/bottom_shadow.dart b/auth/lib/ui/common/bottom_shadow.dart
index 08a73564f..a57e5232a 100644
--- a/auth/lib/ui/common/bottom_shadow.dart
+++ b/auth/lib/ui/common/bottom_shadow.dart
@@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
class BottomShadowWidget extends StatelessWidget {
final double offsetDy;
final Color? shadowColor;
- const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
- : super(key: key);
+ const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/DividerWithPadding.dart b/auth/lib/ui/common/divider_with_padding.dart
similarity index 92%
rename from auth/lib/ui/common/DividerWithPadding.dart
rename to auth/lib/ui/common/divider_with_padding.dart
index 94b20d9c2..0470bdf34 100644
--- a/auth/lib/ui/common/DividerWithPadding.dart
+++ b/auth/lib/ui/common/divider_with_padding.dart
@@ -1,17 +1,15 @@
-
-
import 'package:flutter/material.dart';
class DividerWithPadding extends StatelessWidget {
final double left, top, right, bottom, thinckness;
const DividerWithPadding({
- Key? key,
+ super.key,
this.left = 0,
this.top = 0,
this.right = 0,
this.bottom = 0,
this.thinckness = 0.5,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/dynamic_fab.dart b/auth/lib/ui/common/dynamic_fab.dart
index 0f9771627..962ede0b7 100644
--- a/auth/lib/ui/common/dynamic_fab.dart
+++ b/auth/lib/ui/common/dynamic_fab.dart
@@ -1,3 +1,5 @@
+
+
import 'dart:math' as math;
import 'package:ente_auth/ente_theme_data.dart';
@@ -10,12 +12,12 @@ class DynamicFAB extends StatelessWidget {
final Function? onPressedFunction;
const DynamicFAB({
- Key? key,
+ super.key,
this.isKeypadOpen,
this.buttonText,
this.isFormValid,
this.onPressedFunction,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -60,10 +62,10 @@ class DynamicFAB extends StatelessWidget {
} else {
return Container(
width: double.infinity,
+ height: 56,
padding: const EdgeInsets.symmetric(horizontal: 20),
child: OutlinedButton(
- onPressed:
- isFormValid! ? onPressedFunction as void Function()? : null,
+ onPressed: isFormValid! ? onPressedFunction as void Function()? : null,
child: Text(buttonText!),
),
);
diff --git a/auth/lib/ui/common/gradient_button.dart b/auth/lib/ui/common/gradient_button.dart
index 5b021f613..8a24c6832 100644
--- a/auth/lib/ui/common/gradient_button.dart
+++ b/auth/lib/ui/common/gradient_button.dart
@@ -1,5 +1,3 @@
-
-
import 'package:flutter/material.dart';
class GradientButton extends StatelessWidget {
@@ -15,17 +13,21 @@ class GradientButton extends StatelessWidget {
// padding between the text and icon
final double paddingValue;
+ // used when two icons are in row
+ final bool reversedGradient;
+
const GradientButton({
- Key? key,
+ super.key,
this.linearGradientColors = const [
Color.fromARGB(255, 133, 44, 210),
Color.fromARGB(255, 187, 26, 93),
],
+ this.reversedGradient = false,
this.onTap,
this.text = '',
this.iconData,
this.paddingValue = 0.0,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -71,7 +73,9 @@ class GradientButton extends StatelessWidget {
gradient: LinearGradient(
begin: const Alignment(0.1, -0.9),
end: const Alignment(-0.6, 0.9),
- colors: linearGradientColors,
+ colors: reversedGradient
+ ? linearGradientColors.reversed.toList()
+ : linearGradientColors,
),
borderRadius: BorderRadius.circular(8),
),
diff --git a/auth/lib/ui/common/linear_progress_dialog.dart b/auth/lib/ui/common/linear_progress_dialog.dart
index 85bcc8c5b..08c46d6c9 100644
--- a/auth/lib/ui/common/linear_progress_dialog.dart
+++ b/auth/lib/ui/common/linear_progress_dialog.dart
@@ -1,12 +1,10 @@
-
-
import 'package:ente_auth/ente_theme_data.dart';
import 'package:flutter/material.dart';
class LinearProgressDialog extends StatefulWidget {
final String message;
- const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
+ const LinearProgressDialog(this.message, {super.key});
@override
LinearProgressDialogState createState() => LinearProgressDialogState();
@@ -29,8 +27,8 @@ class LinearProgressDialogState extends State {
@override
Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async => false,
+ return PopScope(
+ canPop: false,
child: AlertDialog(
title: Text(
widget.message,
diff --git a/auth/lib/ui/common/loading_widget.dart b/auth/lib/ui/common/loading_widget.dart
index cf38f3e6b..d37548620 100644
--- a/auth/lib/ui/common/loading_widget.dart
+++ b/auth/lib/ui/common/loading_widget.dart
@@ -11,8 +11,8 @@ class EnteLoadingWidget extends StatelessWidget {
this.size = 14,
this.padding = 5,
this.alignment = Alignment.center,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/common/progress_dialog.dart b/auth/lib/ui/common/progress_dialog.dart
index 8c9a4e1f1..ecd96ae1d 100644
--- a/auth/lib/ui/common/progress_dialog.dart
+++ b/auth/lib/ui/common/progress_dialog.dart
@@ -152,8 +152,8 @@ class ProgressDialog {
barrierColor: _barrierColor,
builder: (BuildContext context) {
_dismissingContext = context;
- return WillPopScope(
- onWillPop: () async => _barrierDismissible,
+ return PopScope(
+ canPop: _barrierDismissible,
child: Dialog(
backgroundColor: _backgroundColor,
insetAnimationCurve: _insetAnimCurve,
diff --git a/auth/lib/ui/common/rename_dialog.dart b/auth/lib/ui/common/rename_dialog.dart
index 03c68d3b1..ad93d1aba 100644
--- a/auth/lib/ui/common/rename_dialog.dart
+++ b/auth/lib/ui/common/rename_dialog.dart
@@ -8,8 +8,7 @@ class RenameDialog extends StatefulWidget {
final String type;
final int maxLength;
- const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100})
- : super(key: key);
+ const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100});
@override
State createState() => _RenameDialogState();
diff --git a/auth/lib/ui/common/web_page.dart b/auth/lib/ui/common/web_page.dart
index 838855c7f..a714bbeb2 100644
--- a/auth/lib/ui/common/web_page.dart
+++ b/auth/lib/ui/common/web_page.dart
@@ -6,7 +6,7 @@ class WebPage extends StatefulWidget {
final String title;
final String url;
- const WebPage(this.title, this.url, {Key? key}) : super(key: key);
+ const WebPage(this.title, this.url, {super.key});
@override
State createState() => _WebPageState();
@@ -28,9 +28,9 @@ class _WebPageState extends State {
),
backgroundColor: Colors.black,
body: InAppWebView(
- initialUrlRequest: URLRequest(url: Uri.parse(widget.url)),
- initialOptions: InAppWebViewGroupOptions(
- crossPlatform: InAppWebViewOptions(transparentBackground: true),
+ initialUrlRequest: URLRequest(url: WebUri(widget.url)),
+ initialSettings: InAppWebViewSettings(
+ transparentBackground: true,
),
onLoadStop: (c, url) {
setState(() {
diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/auth/lib/ui/components/buttons/button_widget.dart
index 28d62f4a6..1932cc02b 100644
--- a/auth/lib/ui/components/buttons/button_widget.dart
+++ b/auth/lib/ui/components/buttons/button_widget.dart
@@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget {
final ValueNotifier? progressStatus;
const ButtonWidget({
- Key? key,
+ super.key,
required this.buttonType,
this.buttonSize = ButtonSize.large,
this.icon,
@@ -71,7 +71,7 @@ class ButtonWidget extends StatelessWidget {
this.shouldSurfaceExecutionStates = true,
this.progressStatus,
this.shouldShowSuccessConfirmation = false,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -155,7 +155,7 @@ class ButtonChildWidget extends StatefulWidget {
final bool shouldShowSuccessConfirmation;
const ButtonChildWidget({
- Key? key,
+ super.key,
required this.buttonStyle,
required this.buttonType,
required this.isDisabled,
@@ -168,7 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
this.labelText,
this.icon,
this.buttonAction,
- }) : super(key: key);
+ });
@override
State createState() => _ButtonChildWidgetState();
diff --git a/auth/lib/ui/components/buttons/icon_button_widget.dart b/auth/lib/ui/components/buttons/icon_button_widget.dart
index 9b0591d4c..eb5554318 100644
--- a/auth/lib/ui/components/buttons/icon_button_widget.dart
+++ b/auth/lib/ui/components/buttons/icon_button_widget.dart
@@ -17,7 +17,7 @@ class IconButtonWidget extends StatefulWidget {
final Color? pressedColor;
final Color? iconColor;
const IconButtonWidget({
- Key? key,
+ super.key,
required this.icon,
required this.iconButtonType,
this.disableGestureDetector = false,
@@ -25,7 +25,7 @@ class IconButtonWidget extends StatefulWidget {
this.defaultColor,
this.pressedColor,
this.iconColor,
- }) : super(key: key);
+ });
@override
State createState() => _IconButtonWidgetState();
diff --git a/auth/lib/ui/components/captioned_text_widget.dart b/auth/lib/ui/components/captioned_text_widget.dart
index f03c8551f..438d906a9 100644
--- a/auth/lib/ui/components/captioned_text_widget.dart
+++ b/auth/lib/ui/components/captioned_text_widget.dart
@@ -13,8 +13,8 @@ class CaptionedTextWidget extends StatelessWidget {
this.textStyle,
this.makeTextBold = false,
this.textColor,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/divider_widget.dart b/auth/lib/ui/components/divider_widget.dart
index 489d59145..657eb024f 100644
--- a/auth/lib/ui/components/divider_widget.dart
+++ b/auth/lib/ui/components/divider_widget.dart
@@ -14,12 +14,12 @@ class DividerWidget extends StatelessWidget {
final bool divColorHasBlur;
final EdgeInsets? padding;
const DividerWidget({
- Key? key,
+ super.key,
required this.dividerType,
this.bgColor = Colors.transparent,
this.divColorHasBlur = true,
this.padding,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/expandable_menu_item_widget.dart b/auth/lib/ui/components/expandable_menu_item_widget.dart
index 9628078be..0d3bce928 100644
--- a/auth/lib/ui/components/expandable_menu_item_widget.dart
+++ b/auth/lib/ui/components/expandable_menu_item_widget.dart
@@ -13,8 +13,8 @@ class ExpandableMenuItemWidget extends StatefulWidget {
required this.title,
required this.selectionOptionsWidget,
required this.leadingIcon,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() =>
diff --git a/auth/lib/ui/components/home_header_widget.dart b/auth/lib/ui/components/home_header_widget.dart
index 268084eef..0079cc7fa 100644
--- a/auth/lib/ui/components/home_header_widget.dart
+++ b/auth/lib/ui/components/home_header_widget.dart
@@ -1,13 +1,10 @@
-import 'dart:ui';
-
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/events/opened_settings_event.dart';
import 'package:flutter/material.dart';
class HomeHeaderWidget extends StatefulWidget {
final Widget centerWidget;
- const HomeHeaderWidget({required this.centerWidget, Key? key})
- : super(key: key);
+ const HomeHeaderWidget({required this.centerWidget, super.key});
@override
State createState() => _HomeHeaderWidgetState();
@@ -16,7 +13,7 @@ class HomeHeaderWidget extends StatefulWidget {
class _HomeHeaderWidgetState extends State {
@override
Widget build(BuildContext context) {
- final hasNotch = window.viewPadding.top > 65;
+ final hasNotch = View.of(context).viewPadding.top > 65;
return Padding(
padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4),
child: Row(
diff --git a/auth/lib/ui/components/menu_item_child_widgets.dart b/auth/lib/ui/components/menu_item_child_widgets.dart
index 81b59bc54..1201f5e1e 100644
--- a/auth/lib/ui/components/menu_item_child_widgets.dart
+++ b/auth/lib/ui/components/menu_item_child_widgets.dart
@@ -12,7 +12,7 @@ class TrailingWidget extends StatefulWidget {
final double trailingExtraMargin;
final bool showExecutionStates;
const TrailingWidget({
- Key? key,
+ super.key,
required this.executionStateNotifier,
this.trailingIcon,
this.trailingIconColor,
@@ -20,7 +20,7 @@ class TrailingWidget extends StatefulWidget {
required this.trailingIconIsMuted,
required this.trailingExtraMargin,
required this.showExecutionStates,
- }) : super(key: key);
+ });
@override
State createState() => _TrailingWidgetState();
}
@@ -101,11 +101,11 @@ class ExpansionTrailingIcon extends StatelessWidget {
final IconData? trailingIcon;
final Color? trailingIconColor;
const ExpansionTrailingIcon({
- Key? key,
+ super.key,
required this.isExpanded,
this.trailingIcon,
this.trailingIconColor,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
@@ -138,12 +138,12 @@ class LeadingWidget extends StatelessWidget {
// leadIconSize deafult value is 20.
final double leadingIconSize;
const LeadingWidget({
- Key? key,
+ super.key,
required this.leadingIconSize,
this.leadingIcon,
this.leadingIconColor,
this.leadingIconWidget,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/menu_item_widget.dart b/auth/lib/ui/components/menu_item_widget.dart
index ef2249f2a..f281edd33 100644
--- a/auth/lib/ui/components/menu_item_widget.dart
+++ b/auth/lib/ui/components/menu_item_widget.dart
@@ -86,8 +86,8 @@ class MenuItemWidget extends StatefulWidget {
this.showOnlyLoadingState = false,
this.surfaceExecutionStates = true,
this.alwaysShowSuccessState = false,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _MenuItemWidgetState();
diff --git a/auth/lib/ui/components/menu_section_description_widget.dart b/auth/lib/ui/components/menu_section_description_widget.dart
index 6e521a536..bcbb9f2fa 100644
--- a/auth/lib/ui/components/menu_section_description_widget.dart
+++ b/auth/lib/ui/components/menu_section_description_widget.dart
@@ -3,8 +3,7 @@ import 'package:flutter/material.dart';
class MenuSectionDescriptionWidget extends StatelessWidget {
final String content;
- const MenuSectionDescriptionWidget({Key? key, required this.content})
- : super(key: key);
+ const MenuSectionDescriptionWidget({super.key, required this.content});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/notification_warning_widget.dart b/auth/lib/ui/components/notification_warning_widget.dart
index a799ffcd3..ab5e6dec1 100644
--- a/auth/lib/ui/components/notification_warning_widget.dart
+++ b/auth/lib/ui/components/notification_warning_widget.dart
@@ -21,14 +21,14 @@ class NotificationWidget extends StatelessWidget {
final NotificationType type;
const NotificationWidget({
- Key? key,
+ super.key,
required this.startIcon,
required this.actionIcon,
required this.text,
required this.onTap,
this.subText,
this.type = NotificationType.warning,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/title_bar_title_widget.dart b/auth/lib/ui/components/title_bar_title_widget.dart
index bc20fe5b1..0aa2534aa 100644
--- a/auth/lib/ui/components/title_bar_title_widget.dart
+++ b/auth/lib/ui/components/title_bar_title_widget.dart
@@ -6,11 +6,11 @@ class TitleBarTitleWidget extends StatelessWidget {
final bool isTitleH2;
final IconData? icon;
const TitleBarTitleWidget({
- Key? key,
+ super.key,
this.title,
this.isTitleH2 = false,
this.icon,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/title_bar_widget.dart b/auth/lib/ui/components/title_bar_widget.dart
index 8e22d4f65..16b532b96 100644
--- a/auth/lib/ui/components/title_bar_widget.dart
+++ b/auth/lib/ui/components/title_bar_widget.dart
@@ -14,7 +14,7 @@ class TitleBarWidget extends StatelessWidget {
final bool isOnTopOfScreen;
final Color? backgroundColor;
const TitleBarWidget({
- Key? key,
+ super.key,
this.leading,
this.title,
this.caption,
@@ -25,7 +25,7 @@ class TitleBarWidget extends StatelessWidget {
this.isFlexibleSpaceDisabled = false,
this.isOnTopOfScreen = true,
this.backgroundColor,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/components/toggle_switch_widget.dart b/auth/lib/ui/components/toggle_switch_widget.dart
index 8ae99f77b..3fc0d153c 100644
--- a/auth/lib/ui/components/toggle_switch_widget.dart
+++ b/auth/lib/ui/components/toggle_switch_widget.dart
@@ -13,8 +13,8 @@ class ToggleSwitchWidget extends StatefulWidget {
const ToggleSwitchWidget({
required this.value,
required this.onChanged,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _ToggleSwitchWidgetState();
diff --git a/auth/lib/ui/home/coach_mark_widget.dart b/auth/lib/ui/home/coach_mark_widget.dart
index f628fabd4..b0e15d7d2 100644
--- a/auth/lib/ui/home/coach_mark_widget.dart
+++ b/auth/lib/ui/home/coach_mark_widget.dart
@@ -21,37 +21,43 @@ class CoachMarkWidget extends StatelessWidget {
children: [
Expanded(
child: Container(
+ width: double.infinity,
color: Theme.of(context).colorScheme.background.withOpacity(0.1),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8),
- child: Column(
+ child: Row(
mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
- const Icon(
- Icons.swipe_left,
- size: 42,
- ),
- const SizedBox(
- height: 12,
- ),
- Text(
- l10n.swipeHint,
- style: Theme.of(context).textTheme.titleLarge,
- textAlign: TextAlign.center,
- ),
- const SizedBox(
- height: 16,
- ),
- SizedBox(
- width: 160,
- child: OutlinedButton(
- onPressed: () async {
- await PreferenceService.instance
- .setHasShownCoachMark(true);
- Bus.instance.fire(CodesUpdatedEvent());
- },
- child: Text(l10n.ok),
- ),
+ Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(
+ Icons.swipe_left,
+ size: 42,
+ ),
+ const SizedBox(
+ height: 24,
+ ),
+ Text(
+ l10n.swipeHint,
+ style: Theme.of(context).textTheme.titleLarge,
+ ),
+ const SizedBox(
+ height: 36,
+ ),
+ SizedBox(
+ width: 160,
+ child: OutlinedButton(
+ onPressed: () async {
+ await PreferenceService.instance
+ .setHasShownCoachMark(true);
+ Bus.instance.fire(CodesUpdatedEvent());
+ },
+ child: Text(l10n.ok),
+ ),
+ ),
+ ],
),
],
),
diff --git a/auth/lib/ui/home/home_empty_state.dart b/auth/lib/ui/home/home_empty_state.dart
index 8a185beef..0768f0491 100644
--- a/auth/lib/ui/home/home_empty_state.dart
+++ b/auth/lib/ui/home/home_empty_state.dart
@@ -3,6 +3,7 @@ import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:ente_auth/ui/settings/faq.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
class HomeEmptyStateWidget extends StatelessWidget {
@@ -10,86 +11,90 @@ class HomeEmptyStateWidget extends StatelessWidget {
final VoidCallback? onManuallySetupTap;
const HomeEmptyStateWidget({
- Key? key,
+ super.key,
required this.onScanTap,
required this.onManuallySetupTap,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return SingleChildScrollView(
child: Center(
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Column(
- children: [
- Image.asset(
- "assets/wallet-front-gradient.png",
- width: 200,
- height: 200,
- ),
- Text(
- l10n.setupFirstAccount,
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.headlineMedium,
- ),
- const SizedBox(height: 64),
- SizedBox(
- width: 400,
- child: OutlinedButton(
- onPressed: onScanTap,
- child: Text(l10n.importScanQrCode),
+ child: ConstrainedBox(
+ constraints: const BoxConstraints.tightFor(height: 800, width: 450),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ children: [
+ Image.asset(
+ "assets/wallet-front-gradient.png",
+ width: 200,
+ height: 200,
),
- ),
- const SizedBox(height: 18),
- SizedBox(
- width: 400,
- child: OutlinedButton(
- onPressed: onManuallySetupTap,
- child: Text(l10n.importEnterSetupKey),
- ),
- ),
- const SizedBox(height: 54),
- InkWell(
- onTap: () {
- routeToPage(context, ImportCodePage());
- },
- child: Text(
- l10n.importCodes,
+ Text(
+ l10n.setupFirstAccount,
textAlign: TextAlign.center,
- style: getEnteTextTheme(context)
- .bodyFaint
- .copyWith(decoration: TextDecoration.underline),
+ style: Theme.of(context).textTheme.headlineMedium,
),
- ),
- const SizedBox(height: 18),
- InkWell(
- onTap: () {
- showModalBottomSheet(
- backgroundColor:
- Theme.of(context).colorScheme.background,
- barrierColor: Colors.black87,
- context: context,
- builder: (context) {
- return const FAQQuestionsWidget();
- },
- );
- },
- child: Text(
- l10n.faq,
- textAlign: TextAlign.center,
- style: getEnteTextTheme(context)
- .bodyFaint
- .copyWith(decoration: TextDecoration.underline),
+ const SizedBox(height: 64),
+ if (PlatformUtil.isMobile())
+ SizedBox(
+ width: 400,
+ child: OutlinedButton(
+ onPressed: onScanTap,
+ child: Text(l10n.importScanQrCode),
+ ),
+ ),
+ const SizedBox(height: 18),
+ SizedBox(
+ width: 400,
+ child: OutlinedButton(
+ onPressed: onManuallySetupTap,
+ child: Text(l10n.importEnterSetupKey),
+ ),
),
- ),
- ],
- ),
- ],
+ const SizedBox(height: 54),
+ InkWell(
+ onTap: () {
+ routeToPage(context, const ImportCodePage());
+ },
+ child: Text(
+ l10n.importCodes,
+ textAlign: TextAlign.center,
+ style: getEnteTextTheme(context)
+ .bodyFaint
+ .copyWith(decoration: TextDecoration.underline),
+ ),
+ ),
+ const SizedBox(height: 18),
+ InkWell(
+ onTap: () {
+ showModalBottomSheet(
+ backgroundColor:
+ Theme.of(context).colorScheme.background,
+ barrierColor: Colors.black87,
+ context: context,
+ builder: (context) {
+ return const FAQQuestionsWidget();
+ },
+ );
+ },
+ child: Text(
+ l10n.faq,
+ textAlign: TextAlign.center,
+ style: getEnteTextTheme(context)
+ .bodyFaint
+ .copyWith(decoration: TextDecoration.underline),
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
),
),
),
diff --git a/auth/lib/ui/home/speed_dial_label_widget.dart b/auth/lib/ui/home/speed_dial_label_widget.dart
index 40c945893..4889ffb6a 100644
--- a/auth/lib/ui/home/speed_dial_label_widget.dart
+++ b/auth/lib/ui/home/speed_dial_label_widget.dart
@@ -6,8 +6,8 @@ class SpeedDialLabelWidget extends StatelessWidget {
const SpeedDialLabelWidget(
this.label, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart
index b8908650f..dfa9a8376 100644
--- a/auth/lib/ui/home_page.dart
+++ b/auth/lib/ui/home_page.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
+import 'package:app_links/app_links.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/event_bus.dart';
import 'package:ente_auth/ente_theme_data.dart';
@@ -22,28 +23,32 @@ import 'package:ente_auth/ui/home/speed_dial_label_widget.dart';
import 'package:ente_auth/ui/scanner_page.dart';
import 'package:ente_auth/ui/settings_page.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/totp_util.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
+import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:logging/logging.dart';
import 'package:move_to_background/move_to_background.dart';
-import 'package:uni_links/uni_links.dart';
class HomePage extends StatefulWidget {
- const HomePage({Key? key}) : super(key: key);
+ const HomePage({super.key});
@override
State createState() => _HomePageState();
}
class _HomePageState extends State {
- static final _settingsPage = SettingsPage(
+ late final _settingsPage = SettingsPage(
emailNotifier: UserService.instance.emailValueNotifier,
+ scaffoldKey: scaffoldKey,
);
bool _hasLoaded = false;
bool _isSettingsOpen = false;
final Logger _logger = Logger("HomePage");
+ final scaffoldKey = GlobalKey();
final TextEditingController _textController = TextEditingController();
bool _showSearchBox = false;
@@ -144,27 +149,24 @@ class _HomePageState extends State {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
- return WillPopScope(
- onWillPop: () async {
+ return PopScope(
+ onPopInvoked: (_) async {
if (_isSettingsOpen) {
- Navigator.pop(context);
- return false;
- }
- if (Platform.isAndroid) {
- MoveToBackground.moveTaskToBack();
- return false;
- } else {
- return true;
+ scaffoldKey.currentState!.closeDrawer();
+ return;
+ } else if (!Platform.isAndroid) {
+ Navigator.of(context).pop();
+ return;
}
+ MoveToBackground.moveTaskToBack();
},
+ canPop: false,
child: Scaffold(
+ key: scaffoldKey,
drawerEnableOpenDragGesture: !Platform.isAndroid,
- drawer: ConstrainedBox(
- constraints: const BoxConstraints(maxWidth: 428),
- child: Drawer(
- width: double.infinity,
- child: _settingsPage,
- ),
+ drawer: Drawer(
+ width: 428,
+ child: _settingsPage,
),
onDrawerChanged: (isOpened) => _isSettingsOpen = isOpened,
body: SafeArea(
@@ -232,10 +234,13 @@ class _HomePageState extends State {
onManuallySetupTap: _redirectToManualEntryPage,
);
} else {
- final list = ListView.builder(
+ final list = AlignedGridView.count(
+ crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 400)
+ .clamp(1, double.infinity)
+ .toInt(),
itemBuilder: ((context, index) {
try {
- return CodeWidget(_filteredCodes[index]);
+ return ClipRect(child: CodeWidget(_filteredCodes[index]));
} catch (e) {
return const Text("Failed");
}
@@ -289,8 +294,10 @@ class _HomePageState extends State {
Future _initDeepLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
+ final appLinks = AppLinks();
try {
- final String? initialLink = await getInitialLink();
+ String? initialLink;
+ initialLink = await appLinks.getInitialAppLinkString();
// Parse the link and warn the user, if it is not correct,
// but keep in mind it could be `null`.
if (initialLink != null) {
@@ -306,14 +313,16 @@ class _HomePageState extends State {
}
// Attach a listener to the stream
- linkStream.listen(
- (String? link) {
- _handleDeeplink(context, link);
- },
- onError: (err) {
- _logger.severe(err);
- },
- );
+ if (!kIsWeb && !Platform.isLinux) {
+ appLinks.stringLinkStream.listen(
+ (link) {
+ _handleDeeplink(context, link);
+ },
+ onError: (err) {
+ _logger.severe(err);
+ },
+ );
+ }
return false;
}
@@ -342,6 +351,14 @@ class _HomePageState extends State {
}
Widget _getFab() {
+ if (PlatformUtil.isDesktop()) {
+ return FloatingActionButton(
+ onPressed: () => _redirectToManualEntryPage(),
+ child: const Icon(Icons.add),
+ elevation: 8.0,
+ shape: const CircleBorder(),
+ );
+ }
return SpeedDial(
icon: Icons.add,
activeIcon: Icons.close,
diff --git a/auth/lib/ui/linear_progress_widget.dart b/auth/lib/ui/linear_progress_widget.dart
index 6296e5d4a..a5dbb2140 100644
--- a/auth/lib/ui/linear_progress_widget.dart
+++ b/auth/lib/ui/linear_progress_widget.dart
@@ -6,8 +6,8 @@ class LinearProgressWidget extends StatelessWidget {
const LinearProgressWidget({
required this.color,
required this.fractionOfStorage,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/scanner_gauth_page.dart b/auth/lib/ui/scanner_gauth_page.dart
index 785f1d3d3..41d0a762d 100644
--- a/auth/lib/ui/scanner_gauth_page.dart
+++ b/auth/lib/ui/scanner_gauth_page.dart
@@ -9,7 +9,7 @@ import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScannerGoogleAuthPage extends StatefulWidget {
- const ScannerGoogleAuthPage({Key? key}) : super(key: key);
+ const ScannerGoogleAuthPage({super.key});
@override
State createState() => ScannerGoogleAuthPageState();
@@ -85,7 +85,7 @@ class ScannerGoogleAuthPageState extends State {
} catch (e) {
controller.dispose();
Navigator.of(context).pop();
- showToast(context, "Error " + e.toString());
+ showToast(context, "Error $e");
}
});
}
diff --git a/auth/lib/ui/scanner_page.dart b/auth/lib/ui/scanner_page.dart
index 0159f0cfa..6a7793631 100644
--- a/auth/lib/ui/scanner_page.dart
+++ b/auth/lib/ui/scanner_page.dart
@@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
class ScannerPage extends StatefulWidget {
- const ScannerPage({Key? key}) : super(key: key);
+ const ScannerPage({super.key});
@override
State createState() => ScannerPageState();
diff --git a/auth/lib/ui/settings/about_section_widget.dart b/auth/lib/ui/settings/about_section_widget.dart
index 7c7b46377..98a24d816 100644
--- a/auth/lib/ui/settings/about_section_widget.dart
+++ b/auth/lib/ui/settings/about_section_widget.dart
@@ -1,19 +1,19 @@
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/update_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
-import 'package:ente_auth/ui/common/web_page.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/app_update_dialog.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class AboutSectionWidget extends StatelessWidget {
- const AboutSectionWidget({Key? key}) : super(key: key);
+ const AboutSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -36,7 +36,7 @@ class AboutSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
- launchUrl(Uri.parse("https://github.com/ente-io/ente"));
+ launchUrl(Uri.parse("https://github.com/ente-io/auth"));
},
),
sectionOptionSpacing,
@@ -102,8 +102,8 @@ class AboutMenuItemWidget extends StatelessWidget {
required this.title,
required this.url,
this.webPageTitle,
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -115,12 +115,10 @@ class AboutMenuItemWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return WebPage(webPageTitle ?? title, url);
- },
- ),
+ await PlatformUtil.openWebView(
+ context,
+ webPageTitle ?? title,
+ url,
);
},
);
diff --git a/auth/lib/ui/settings/account_section_widget.dart b/auth/lib/ui/settings/account_section_widget.dart
index 13e31e037..1718f276a 100644
--- a/auth/lib/ui/settings/account_section_widget.dart
+++ b/auth/lib/ui/settings/account_section_widget.dart
@@ -1,22 +1,20 @@
-
-
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/services/local_authentication_service.dart';
import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/account/change_email_dialog.dart';
import 'package:ente_auth/ui/account/delete_account_page.dart';
-import 'package:ente_auth/ui/account/password_entry_page.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
class AccountSectionWidget extends StatelessWidget {
- AccountSectionWidget({Key? key}) : super(key: key);
+ AccountSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -46,6 +44,7 @@ class AccountSectionWidget extends StatelessWidget {
context,
l10n.authToChangeYourEmail,
);
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
showDialog(
context: context,
@@ -59,33 +58,34 @@ class AccountSectionWidget extends StatelessWidget {
},
),
sectionOptionSpacing,
- MenuItemWidget(
- captionedTextWidget: CaptionedTextWidget(
- title: l10n.changePassword,
- ),
- pressedColor: getEnteColorScheme(context).fillFaint,
- trailingIcon: Icons.chevron_right_outlined,
- trailingIconIsMuted: true,
- onTap: () async {
- final hasAuthenticated = await LocalAuthenticationService.instance
- .requestLocalAuthentication(
- context,
- l10n.authToChangeYourPassword,
- );
- if (hasAuthenticated) {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return const PasswordEntryPage(
- mode: PasswordEntryMode.update,
- );
- },
- ),
- );
- }
- },
- ),
- sectionOptionSpacing,
+ // TODO: Remove After Stable
+ // MenuItemWidget(
+ // captionedTextWidget: CaptionedTextWidget(
+ // title: l10n.changePassword,
+ // ),
+ // pressedColor: getEnteColorScheme(context).fillFaint,
+ // trailingIcon: Icons.chevron_right_outlined,
+ // trailingIconIsMuted: true,
+ // onTap: () async {
+ // final hasAuthenticated = await LocalAuthenticationService.instance
+ // .requestLocalAuthentication(
+ // context,
+ // l10n.authToChangeYourPassword,
+ // );
+ // if (hasAuthenticated) {
+ // Navigator.of(context).push(
+ // MaterialPageRoute(
+ // builder: (BuildContext context) {
+ // return const PasswordEntryPage(
+ // mode: PasswordEntryMode.update,
+ // );
+ // },
+ // ),
+ // );
+ // }
+ // },
+ // ),
+ // sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: context.l10n.logout,
@@ -115,6 +115,7 @@ class AccountSectionWidget extends StatelessWidget {
children: children,
);
}
+
void _onLogoutTapped(BuildContext context) {
showChoiceActionSheet(
context,
diff --git a/auth/lib/ui/settings/app_update_dialog.dart b/auth/lib/ui/settings/app_update_dialog.dart
index eba357285..6fbccbe43 100644
--- a/auth/lib/ui/settings/app_update_dialog.dart
+++ b/auth/lib/ui/settings/app_update_dialog.dart
@@ -14,7 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart';
class AppUpdateDialog extends StatefulWidget {
final LatestVersionInfo? latestVersionInfo;
- const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key);
+ const AppUpdateDialog(this.latestVersionInfo, {super.key});
@override
State createState() => _AppUpdateDialogState();
@@ -30,7 +30,7 @@ class _AppUpdateDialogState extends State {
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 4),
child: Text(
- "- " + log,
+ "- $log",
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 14,
),
@@ -87,8 +87,8 @@ class _AppUpdateDialogState extends State {
);
final shouldForceUpdate =
UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo);
- return WillPopScope(
- onWillPop: () async => !shouldForceUpdate,
+ return PopScope(
+ canPop: !shouldForceUpdate,
child: AlertDialog(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -118,7 +118,7 @@ class _AppUpdateDialogState extends State {
class ApkDownloaderDialog extends StatefulWidget {
final LatestVersionInfo? versionInfo;
- const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
+ const ApkDownloaderDialog(this.versionInfo, {super.key});
@override
State createState() => _ApkDownloaderDialogState();
@@ -131,17 +131,15 @@ class _ApkDownloaderDialogState extends State {
@override
void initState() {
super.initState();
- _saveUrl = Configuration.instance.getTempDirectory() +
- "ente-" +
- widget.versionInfo!.name! +
- ".apk";
+ _saveUrl =
+ "${Configuration.instance.getTempDirectory()}ente-${widget.versionInfo!.name!}.apk";
_downloadApk();
}
@override
Widget build(BuildContext context) {
- return WillPopScope(
- onWillPop: () async => false,
+ return PopScope(
+ canPop: false,
child: AlertDialog(
title: const Text(
"Downloading...",
diff --git a/auth/lib/ui/settings/app_version_widget.dart b/auth/lib/ui/settings/app_version_widget.dart
index 3b15c6480..58c693f7c 100644
--- a/auth/lib/ui/settings/app_version_widget.dart
+++ b/auth/lib/ui/settings/app_version_widget.dart
@@ -4,8 +4,8 @@ import 'package:package_info_plus/package_info_plus.dart';
class AppVersionWidget extends StatefulWidget {
const AppVersionWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _AppVersionWidgetState();
@@ -48,7 +48,7 @@ class _AppVersionWidgetState extends State {
return Padding(
padding: const EdgeInsets.all(20),
child: Text(
- "Version: " + snapshot.data!,
+ "Version: ${snapshot.data!}",
style: Theme.of(context).textTheme.bodySmall,
),
);
diff --git a/auth/lib/ui/settings/danger_section_widget.dart b/auth/lib/ui/settings/danger_section_widget.dart
index bdf16a167..ae9953917 100644
--- a/auth/lib/ui/settings/danger_section_widget.dart
+++ b/auth/lib/ui/settings/danger_section_widget.dart
@@ -11,7 +11,7 @@ import 'package:ente_auth/utils/navigation_util.dart';
import 'package:flutter/material.dart';
class DangerSectionWidget extends StatelessWidget {
- const DangerSectionWidget({Key? key}) : super(key: key);
+ const DangerSectionWidget({super.key});
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/data/data_section_widget.dart b/auth/lib/ui/settings/data/data_section_widget.dart
index b30bf7447..db3d2aae2 100644
--- a/auth/lib/ui/settings/data/data_section_widget.dart
+++ b/auth/lib/ui/settings/data/data_section_widget.dart
@@ -1,4 +1,3 @@
-
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/theme/ente_theme.dart';
import 'package:ente_auth/ui/components/captioned_text_widget.dart';
@@ -11,7 +10,9 @@ import 'package:ente_auth/utils/navigation_util.dart';
import 'package:flutter/material.dart';
class DataSectionWidget extends StatelessWidget {
- DataSectionWidget({Key? key}) : super(key: key);
+ // final _logger = Logger("AccountSectionWidget");
+
+ DataSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -36,7 +37,7 @@ class DataSectionWidget extends StatelessWidget {
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
- routeToPage(context, ImportCodePage());
+ routeToPage(context, const ImportCodePage());
},
),
sectionOptionSpacing,
diff --git a/auth/lib/ui/settings/data/export_widget.dart b/auth/lib/ui/settings/data/export_widget.dart
index cc9a1638b..e7d42cd98 100644
--- a/auth/lib/ui/settings/data/export_widget.dart
+++ b/auth/lib/ui/settings/data/export_widget.dart
@@ -9,14 +9,14 @@ import 'package:ente_auth/store/code_store.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/share_utils.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/cupertino.dart';
-import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:share_plus/share_plus.dart';
@@ -76,17 +76,17 @@ Future _requestForEncryptionPassword(
try {
final kekSalt = CryptoUtil.getSaltToDeriveKey();
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
- utf8.encode(password) as Uint8List,
+ utf8.encode(password),
kekSalt,
);
String exportPlainText = await _getAuthDataForExport();
// Encrypt the key with this derived key
- final encResult = await CryptoUtil.encryptChaCha(
- utf8.encode(exportPlainText) as Uint8List,
+ final encResult = await CryptoUtil.encryptData(
+ utf8.encode(exportPlainText),
derivedKeyResult.key,
);
- final encContent = Sodium.bin2base64(encResult.encryptedData!);
- final encNonce = Sodium.bin2base64(encResult.header!);
+ final encContent = CryptoUtil.bin2base64(encResult.encryptedData!);
+ final encNonce = CryptoUtil.bin2base64(encResult.header!);
final EnteAuthExport data = EnteAuthExport(
version: 1,
encryptedData: encContent,
@@ -94,11 +94,11 @@ Future _requestForEncryptionPassword(
kdfParams: KDFParams(
memLimit: derivedKeyResult.memLimit,
opsLimit: derivedKeyResult.opsLimit,
- salt: Sodium.bin2base64(kekSalt),
+ salt: CryptoUtil.bin2base64(kekSalt),
),
);
// get json value of data
- _exportCodes(context, jsonEncode(data.toJson()));
+ await _exportCodes(context, jsonEncode(data.toJson()));
} catch (e, s) {
Logger("ExportWidget").severe(e, s);
showToast(context, "Error while exporting codes.");
@@ -126,46 +126,55 @@ Future _showExportWarningDialog(BuildContext context) async {
Future _exportCodes(BuildContext context, String fileContent) async {
DateTime now = DateTime.now().toUtc();
String formattedDate = DateFormat('yyyy-MM-dd').format(now);
- String exportFileName = 'ente-auth-codes-$formattedDate.txt';
- final _codeFile = File(
- Configuration.instance.getTempDirectory() + exportFileName,
- );
+ String exportFileName = 'ente-auth-codes-$formattedDate';
+ String exportFileExtension = 'txt';
final hasAuthenticated = await LocalAuthenticationService.instance
.requestLocalAuthentication(context, context.l10n.authToExportCodes);
+ await PlatformUtil.refocusWindows();
if (!hasAuthenticated) {
return;
}
- if (_codeFile.existsSync()) {
- await _codeFile.delete();
- }
- _codeFile.writeAsStringSync(fileContent);
- final Size size = MediaQuery.of(context).size;
-
- if (Platform.isAndroid) {
- await FileSaver.instance.saveAs(
- name: exportFileName,
- filePath: _codeFile.path,
- mimeType: MimeType.text,
- ext: 'txt',
- );
- } else {
- await Share.shareFiles(
- [_codeFile.path],
- sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
- );
- }
- Future.delayed(const Duration(seconds: 30), () async {
- if (_codeFile.existsSync()) {
- _codeFile.deleteSync();
- }
- });
+ Future.delayed(
+ const Duration(milliseconds: 1200),
+ () async => await shareDialog(
+ context,
+ context.l10n.exportCodes,
+ saveAction: () async {
+ await PlatformUtil.shareFile(
+ exportFileName,
+ exportFileExtension,
+ CryptoUtil.strToBin(fileContent),
+ MimeType.text,
+ );
+ },
+ sendAction: () async {
+ final codeFile = File(
+ "${Configuration.instance.getTempDirectory()}$exportFileName.$exportFileExtension",
+ );
+ if (codeFile.existsSync()) {
+ await codeFile.delete();
+ }
+ codeFile.writeAsStringSync(fileContent);
+ final Size size = MediaQuery.of(context).size;
+ await Share.shareFiles(
+ [codeFile.path],
+ sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2),
+ );
+ Future.delayed(const Duration(seconds: 30), () async {
+ if (codeFile.existsSync()) {
+ codeFile.deleteSync();
+ }
+ });
+ },
+ ),
+ );
}
Future _getAuthDataForExport() async {
final codes = await CodeStore.instance.getAllCodes();
String data = "";
for (final code in codes) {
- data += code.rawData + "\n";
+ data += "${code.rawData}\n";
}
return data;
}
diff --git a/auth/lib/ui/settings/data/import/aegis_import.dart b/auth/lib/ui/settings/data/import/aegis_import.dart
index 5042d618f..b801e64a5 100644
--- a/auth/lib/ui/settings/data/import/aegis_import.dart
+++ b/auth/lib/ui/settings/data/import/aegis_import.dart
@@ -91,7 +91,7 @@ Future _processAegisExportFile(
final jsonString = await file.readAsString();
final decodedJson = jsonDecode(jsonString);
final isEncrypted = decodedJson['header']['slots'] != null;
- var aegisDB;
+ Map? aegisDB;
if (isEncrypted) {
String? password;
try {
@@ -127,7 +127,7 @@ Future _processAegisExportFile(
aegisDB = decodedJson['db'];
}
final parsedCodes = [];
- for (var item in aegisDB['entries']) {
+ for (var item in aegisDB?['entries']) {
var kind = item['type'];
var account = item['name'];
var issuer = item['issuer'];
diff --git a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
index 15f869206..0cba96056 100644
--- a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
+++ b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart
@@ -11,14 +11,13 @@ import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
import 'package:logging/logging.dart';
Future showEncryptedImportInstruction(BuildContext context) async {
@@ -80,21 +79,21 @@ Future _decryptExportData(
try {
await progressDialog.show();
final derivedKey = await CryptoUtil.deriveKey(
- utf8.encode(password) as Uint8List,
- Sodium.base642bin(enteAuthExport.kdfParams.salt),
+ utf8.encode(password),
+ CryptoUtil.base642bin(enteAuthExport.kdfParams.salt),
enteAuthExport.kdfParams.memLimit,
enteAuthExport.kdfParams.opsLimit,
);
Uint8List? decryptedContent;
// Encrypt the key with this derived key
try {
- decryptedContent = await CryptoUtil.decryptChaCha(
- Sodium.base642bin(enteAuthExport.encryptedData),
+ decryptedContent = await CryptoUtil.decryptData(
+ CryptoUtil.base642bin(enteAuthExport.encryptedData),
derivedKey,
- Sodium.base642bin(enteAuthExport.encryptionNonce),
+ CryptoUtil.base642bin(enteAuthExport.encryptionNonce),
);
- } catch (e,s) {
- Logger("encryptedImport").warning('failed to decrypt',e,s);
+ } catch (e, s) {
+ Logger("encryptedImport").warning('failed to decrypt', e, s);
showToast(context, l10n.incorrectPasswordTitle);
isPasswordIncorrect = true;
}
diff --git a/auth/lib/ui/settings/data/import/google_auth_import.dart b/auth/lib/ui/settings/data/import/google_auth_import.dart
index d883f365c..60638f91c 100644
--- a/auth/lib/ui/settings/data/import/google_auth_import.dart
+++ b/auth/lib/ui/settings/data/import/google_auth_import.dart
@@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:typed_data';
import 'package:base32/base32.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/models/code.dart';
@@ -12,6 +11,8 @@ import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/scanner_gauth_page.dart';
import 'package:ente_auth/ui/settings/data/import/import_success.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
@@ -24,13 +25,14 @@ Future showGoogleAuthInstruction(BuildContext context) async {
title: l10n.importFromApp("Google Authenticator"),
body: l10n.importGoogleAuthGuide,
buttons: [
- ButtonWidget(
- buttonType: ButtonType.primary,
- labelText: l10n.scanAQrCode,
- isInAlert: true,
- buttonSize: ButtonSize.large,
- buttonAction: ButtonAction.first,
- ),
+ if (PlatformUtil.isMobile())
+ ButtonWidget(
+ buttonType: ButtonType.primary,
+ labelText: l10n.scanAQrCode,
+ isInAlert: true,
+ buttonSize: ButtonSize.large,
+ buttonAction: ButtonAction.first,
+ ),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: context.l10n.cancel,
diff --git a/auth/lib/ui/settings/data/import/import_service.dart b/auth/lib/ui/settings/data/import/import_service.dart
index ab48924be..a510ec88b 100644
--- a/auth/lib/ui/settings/data/import/import_service.dart
+++ b/auth/lib/ui/settings/data/import/import_service.dart
@@ -1,4 +1,3 @@
-import 'package:ente_auth/ui/settings/data/import/2fas_import.dart';
import 'package:ente_auth/ui/settings/data/import/aegis_import.dart';
import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart';
import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart';
@@ -6,6 +5,7 @@ import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart';
import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart';
import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart';
import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart';
+import 'package:ente_auth/ui/settings/data/import/two_fas_import.dart';
import 'package:ente_auth/ui/settings/data/import_page.dart';
import 'package:flutter/cupertino.dart';
diff --git a/auth/lib/ui/settings/data/import/import_success.dart b/auth/lib/ui/settings/data/import/import_success.dart
index cc77248e8..5827248f8 100644
--- a/auth/lib/ui/settings/data/import/import_success.dart
+++ b/auth/lib/ui/settings/data/import/import_success.dart
@@ -11,9 +11,9 @@ Future importSuccessDialog(BuildContext context, int count) async {
firstButtonLabel: context.l10n.ok,
firstButtonOnTap: () async {
Navigator.of(context).pop();
- if(Navigator.of(context).canPop()) {
- Navigator.of(context).pop();
- }
+ // if(Navigator.of(context).canPop()) {
+ // Navigator.of(context).pop();
+ // }
},
firstButtonType: ButtonType.primary,
);
diff --git a/auth/lib/ui/settings/data/import/plain_text_import.dart b/auth/lib/ui/settings/data/import/plain_text_import.dart
index a8e64bb5f..d76effd8c 100644
--- a/auth/lib/ui/settings/data/import/plain_text_import.dart
+++ b/auth/lib/ui/settings/data/import/plain_text_import.dart
@@ -48,10 +48,8 @@ class PlainTextImport extends StatelessWidget {
],
);
}
-
}
-
Future showImportInstructionDialog(BuildContext context) async {
final l10n = context.l10n;
final AlertDialog alert = AlertDialog(
@@ -94,7 +92,6 @@ Future showImportInstructionDialog(BuildContext context) async {
);
}
-
Future _pickImportFile(BuildContext context) async {
final l10n = context.l10n;
FilePickerResult? result = await FilePicker.platform.pickFiles();
diff --git a/auth/lib/ui/settings/data/import/2fas_import.dart b/auth/lib/ui/settings/data/import/two_fas_import.dart
similarity index 98%
rename from auth/lib/ui/settings/data/import/2fas_import.dart
rename to auth/lib/ui/settings/data/import/two_fas_import.dart
index b6e75f3c6..ae5a05b0b 100644
--- a/auth/lib/ui/settings/data/import/2fas_import.dart
+++ b/auth/lib/ui/settings/data/import/two_fas_import.dart
@@ -170,8 +170,8 @@ Future _process2FasExportFile(
}
String decrypt2FasVault(dynamic data, {required String password}) {
- int ITERATION_COUNT = 10000;
- int KEY_SIZE = 256;
+ int iterationCount = 10000;
+ int keySize = 256;
final String encryptedServices = data["servicesEncrypted"];
var split = encryptedServices.split(":");
final encryptedData = base64.decode(split[0]);
@@ -181,11 +181,11 @@ String decrypt2FasVault(dynamic data, {required String password}) {
final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
final params = Pbkdf2Parameters(
salt,
- ITERATION_COUNT,
- KEY_SIZE ~/ 8,
+ iterationCount,
+ keySize ~/ 8,
);
pbkdf2.init(params);
- Uint8List key = Uint8List(KEY_SIZE ~/ 8);
+ Uint8List key = Uint8List(keySize ~/ 8);
pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0);
final decrypted = decrypt(key, iv, encryptedData);
final utf8Decode = utf8.decode(decrypted);
diff --git a/auth/lib/ui/settings/data/import_page.dart b/auth/lib/ui/settings/data/import_page.dart
index 08a4d40d8..b7ac09846 100644
--- a/auth/lib/ui/settings/data/import_page.dart
+++ b/auth/lib/ui/settings/data/import_page.dart
@@ -21,7 +21,9 @@ enum ImportType {
}
class ImportCodePage extends StatelessWidget {
- final List importOptions = [
+ const ImportCodePage({super.key});
+
+ static const List importOptions = [
ImportType.plainText,
ImportType.encrypted,
ImportType.twoFas,
@@ -32,8 +34,6 @@ class ImportCodePage extends StatelessWidget {
ImportType.lastpass,
];
- ImportCodePage({super.key});
-
String getTitle(BuildContext context, ImportType type) {
switch (type) {
case ImportType.plainText:
diff --git a/auth/lib/ui/settings/debug_section_widget.dart b/auth/lib/ui/settings/debug_section_widget.dart
index 259937ff7..03406f791 100644
--- a/auth/lib/ui/settings/debug_section_widget.dart
+++ b/auth/lib/ui/settings/debug_section_widget.dart
@@ -3,9 +3,9 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
import 'package:ente_auth/ui/settings/settings_section_title.dart';
import 'package:ente_auth/ui/settings/settings_text_item.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class DebugSectionWidget extends StatelessWidget {
const DebugSectionWidget({super.key});
@@ -51,7 +51,7 @@ class DebugSectionWidget extends StatelessWidget {
"Key",
style: TextStyle(fontWeight: FontWeight.bold),
),
- Text(Sodium.bin2base64(Configuration.instance.getKey()!)),
+ Text(CryptoUtil.bin2base64(Configuration.instance.getKey()!)),
const Padding(padding: EdgeInsets.all(12)),
const Text(
"Encrypted Key",
diff --git a/auth/lib/ui/settings/faq.dart b/auth/lib/ui/settings/faq.dart
index d9aa88f1b..5f441912e 100644
--- a/auth/lib/ui/settings/faq.dart
+++ b/auth/lib/ui/settings/faq.dart
@@ -8,8 +8,8 @@ import 'package:flutter/material.dart';
class FAQQuestionsWidget extends StatelessWidget {
const FAQQuestionsWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -64,9 +64,9 @@ class FAQQuestionsWidget extends StatelessWidget {
class FaqWidget extends StatelessWidget {
const FaqWidget({
- Key? key,
+ super.key,
required this.faq,
- }) : super(key: key);
+ });
final FaqItem? faq;
diff --git a/auth/lib/ui/settings/general_section_widget.dart b/auth/lib/ui/settings/general_section_widget.dart
index 5026166f6..7172da13c 100644
--- a/auth/lib/ui/settings/general_section_widget.dart
+++ b/auth/lib/ui/settings/general_section_widget.dart
@@ -17,7 +17,7 @@ import 'package:ente_auth/utils/toast_util.dart';
import 'package:flutter/material.dart';
class AdvancedSectionWidget extends StatefulWidget {
- const AdvancedSectionWidget({Key? key}) : super(key: key);
+ const AdvancedSectionWidget({super.key});
@override
State createState() => _AdvancedSectionWidgetState();
diff --git a/auth/lib/ui/settings/language_picker.dart b/auth/lib/ui/settings/language_picker.dart
index 997e53068..3e97813fd 100644
--- a/auth/lib/ui/settings/language_picker.dart
+++ b/auth/lib/ui/settings/language_picker.dart
@@ -18,8 +18,8 @@ class LanguageSelectorPage extends StatelessWidget {
this.supportedLocales,
this.onLocaleChanged,
this.currentLocale, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -79,8 +79,8 @@ class ItemsWidget extends StatefulWidget {
this.supportedLocales,
this.onLocaleChanged,
this.currentLocale, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _ItemsWidgetState();
diff --git a/auth/lib/ui/settings/made_with_love_widget.dart b/auth/lib/ui/settings/made_with_love_widget.dart
index ff1715efd..a8e12c106 100644
--- a/auth/lib/ui/settings/made_with_love_widget.dart
+++ b/auth/lib/ui/settings/made_with_love_widget.dart
@@ -4,8 +4,8 @@ import 'package:url_launcher/url_launcher.dart';
class MadeWithLoveWidget extends StatelessWidget {
const MadeWithLoveWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/security_section_widget.dart b/auth/lib/ui/settings/security_section_widget.dart
index aa05dd62b..b3d3914c1 100644
--- a/auth/lib/ui/settings/security_section_widget.dart
+++ b/auth/lib/ui/settings/security_section_widget.dart
@@ -15,15 +15,15 @@ import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart';
import 'package:ente_auth/ui/components/menu_item_widget.dart';
import 'package:ente_auth/ui/components/toggle_switch_widget.dart';
import 'package:ente_auth/ui/settings/common_settings.dart';
-import 'package:ente_auth/utils/crypto_util.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:ente_auth/utils/toast_util.dart';
+import 'package:ente_crypto_dart/ente_crypto_dart.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
class SecuritySectionWidget extends StatefulWidget {
- const SecuritySectionWidget({Key? key}) : super(key: key);
+ const SecuritySectionWidget({super.key});
@override
State createState() => _SecuritySectionWidgetState();
@@ -78,11 +78,12 @@ class _SecuritySectionWidgetState extends State {
context,
l10n.authToViewYourRecoveryKey,
);
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
String recoveryKey;
try {
recoveryKey =
- Sodium.bin2hex(Configuration.instance.getRecoveryKey());
+ CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
} catch (e) {
showGenericErrorDialog(context: context);
return;
@@ -113,6 +114,7 @@ class _SecuritySectionWidgetState extends State {
);
final isEmailMFAEnabled =
UserService.instance.hasEmailMFAEnabled();
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
await updateEmailMFA(!isEmailMFAEnabled);
if (mounted) {
@@ -136,6 +138,7 @@ class _SecuritySectionWidgetState extends State {
context,
context.l10n.authToViewYourActiveSessions,
);
+ await PlatformUtil.refocusWindows();
if (hasAuthenticated) {
Navigator.of(context).push(
MaterialPageRoute(
@@ -167,6 +170,7 @@ class _SecuritySectionWidgetState extends State {
context.l10n.lockScreenEnablePreSteps,
);
if (hasAuthenticated) {
+ FocusScope.of(context).requestFocus();
setState(() {});
}
},
diff --git a/auth/lib/ui/settings/settings_section_title.dart b/auth/lib/ui/settings/settings_section_title.dart
index 08dbc69ec..8b7e549cf 100644
--- a/auth/lib/ui/settings/settings_section_title.dart
+++ b/auth/lib/ui/settings/settings_section_title.dart
@@ -8,9 +8,9 @@ class SettingsSectionTitle extends StatelessWidget {
const SettingsSectionTitle(
this.title, {
- Key? key,
+ super.key,
this.color,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/settings_text_item.dart b/auth/lib/ui/settings/settings_text_item.dart
index 6273c7cad..afaf56432 100644
--- a/auth/lib/ui/settings/settings_text_item.dart
+++ b/auth/lib/ui/settings/settings_text_item.dart
@@ -8,10 +8,10 @@ class SettingsTextItem extends StatelessWidget {
final String text;
final IconData icon;
const SettingsTextItem({
- Key? key,
+ super.key,
required this.text,
required this.icon,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/social_section_widget.dart b/auth/lib/ui/settings/social_section_widget.dart
index 71016bf32..8e1655ac7 100644
--- a/auth/lib/ui/settings/social_section_widget.dart
+++ b/auth/lib/ui/settings/social_section_widget.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SocialSectionWidget extends StatelessWidget {
- const SocialSectionWidget({Key? key}) : super(key: key);
+ const SocialSectionWidget({super.key});
@override
Widget build(BuildContext context) {
@@ -67,9 +67,9 @@ class SocialsMenuItemWidget extends StatelessWidget {
const SocialsMenuItemWidget(
this.text,
this.url, {
- Key? key,
+ super.key,
this.launchInExternalApp = true,
- }) : super(key: key);
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/support_dev_widget.dart b/auth/lib/ui/settings/support_dev_widget.dart
index 63fa92c27..849b95415 100644
--- a/auth/lib/ui/settings/support_dev_widget.dart
+++ b/auth/lib/ui/settings/support_dev_widget.dart
@@ -10,8 +10,8 @@ import 'package:url_launcher/url_launcher.dart';
class SupportDevWidget extends StatelessWidget {
const SupportDevWidget({
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
Widget build(BuildContext context) {
diff --git a/auth/lib/ui/settings/support_section_widget.dart b/auth/lib/ui/settings/support_section_widget.dart
index 8c3d1e8fe..77497be2a 100644
--- a/auth/lib/ui/settings/support_section_widget.dart
+++ b/auth/lib/ui/settings/support_section_widget.dart
@@ -12,7 +12,7 @@ import 'package:logging/logging.dart';
import 'package:url_launcher/url_launcher_string.dart';
class SupportSectionWidget extends StatefulWidget {
- const SupportSectionWidget({Key? key}) : super(key: key);
+ const SupportSectionWidget({super.key});
@override
State createState() => _SupportSectionWidgetState();
@@ -62,7 +62,7 @@ class _SupportSectionWidgetState extends State {
trailingIconIsMuted: true,
onTap: () async {
launchUrlString(
- githubDiscussionsUrl,
+ githubIssuesUrl,
mode: LaunchMode.externalApplication,
);
},
diff --git a/auth/lib/ui/settings/theme_switch_widget.dart b/auth/lib/ui/settings/theme_switch_widget.dart
index 69e4513f1..c0495b65b 100644
--- a/auth/lib/ui/settings/theme_switch_widget.dart
+++ b/auth/lib/ui/settings/theme_switch_widget.dart
@@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class ThemeSwitchWidget extends StatefulWidget {
- const ThemeSwitchWidget({Key? key}) : super(key: key);
+ const ThemeSwitchWidget({super.key});
@override
State createState() => _ThemeSwitchWidgetState();
diff --git a/auth/lib/ui/settings/title_bar_widget.dart b/auth/lib/ui/settings/title_bar_widget.dart
index e11a5c287..8d9aff85c 100644
--- a/auth/lib/ui/settings/title_bar_widget.dart
+++ b/auth/lib/ui/settings/title_bar_widget.dart
@@ -2,7 +2,12 @@ import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/material.dart';
class SettingsTitleBarWidget extends StatelessWidget {
- const SettingsTitleBarWidget({Key? key}) : super(key: key);
+ const SettingsTitleBarWidget({
+ super.key,
+ required this.scaffoldKey,
+ });
+
+ final GlobalKey scaffoldKey;
@override
Widget build(BuildContext context) {
@@ -17,7 +22,7 @@ class SettingsTitleBarWidget extends StatelessWidget {
IconButton(
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
onPressed: () {
- Navigator.pop(context);
+ scaffoldKey.currentState?.closeDrawer();
},
icon: const Icon(Icons.keyboard_double_arrow_left_outlined),
),
diff --git a/auth/lib/ui/settings_page.dart b/auth/lib/ui/settings_page.dart
index e5df8fcc3..5f516d7cc 100644
--- a/auth/lib/ui/settings_page.dart
+++ b/auth/lib/ui/settings_page.dart
@@ -25,18 +25,24 @@ import 'package:ente_auth/ui/settings/theme_switch_widget.dart';
import 'package:ente_auth/ui/settings/title_bar_widget.dart';
import 'package:ente_auth/utils/dialog_util.dart';
import 'package:ente_auth/utils/navigation_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
final ValueNotifier emailNotifier;
+ final GlobalKey scaffoldKey;
- SettingsPage({Key? key, required this.emailNotifier}) : super(key: key);
+ SettingsPage({
+ super.key,
+ required this.emailNotifier,
+ required this.scaffoldKey,
+ });
@override
Widget build(BuildContext context) {
- final _hasLoggedIn = Configuration.instance.hasConfiguredAccount();
- if (_hasLoggedIn) {
+ final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
+ if (hasLoggedIn) {
UserService.instance.getUserDetailsV2().ignore();
}
final enteColorScheme = getEnteColorScheme(context);
@@ -49,11 +55,11 @@ class SettingsPage extends StatelessWidget {
}
Widget _getBody(BuildContext context, EnteColorScheme colorScheme) {
- final _hasLoggedIn = Configuration.instance.hasConfiguredAccount();
+ final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
final enteTextTheme = getEnteTextTheme(context);
const sectionSpacing = SizedBox(height: 8);
final List contents = [];
- if (_hasLoggedIn) {
+ if (hasLoggedIn) {
contents.add(
Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
@@ -110,6 +116,7 @@ class SettingsPage extends StatelessWidget {
context,
context.l10n.authToInitiateSignIn,
);
+ await PlatformUtil.refocusWindows();
if (!hasAuthenticated) {
return;
}
@@ -161,7 +168,9 @@ class SettingsPage extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const SettingsTitleBarWidget(),
+ SettingsTitleBarWidget(
+ scaffoldKey: scaffoldKey,
+ ),
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
child: Column(
diff --git a/auth/lib/ui/tools/app_lock.dart b/auth/lib/ui/tools/app_lock.dart
index 2e7e0f54b..df55f8116 100644
--- a/auth/lib/ui/tools/app_lock.dart
+++ b/auth/lib/ui/tools/app_lock.dart
@@ -37,7 +37,7 @@ class AppLock extends StatefulWidget {
final Locale locale;
const AppLock({
- Key? key,
+ super.key,
required this.builder,
required this.lockScreen,
required this.savedThemeMode,
@@ -46,7 +46,7 @@ class AppLock extends StatefulWidget {
this.backgroundLockLatency = const Duration(seconds: 0),
this.darkTheme,
this.lightTheme,
- }) : super(key: key);
+ });
static _AppLockState? of(BuildContext context) =>
context.findAncestorStateOfType<_AppLockState>();
@@ -135,9 +135,9 @@ class _AppLockState extends State with WidgetsBindingObserver {
}
Widget get _lockScreen {
- return WillPopScope(
+ return PopScope(
child: this.widget.lockScreen,
- onWillPop: () => Future.value(false),
+ canPop: false,
);
}
diff --git a/auth/lib/ui/tools/debug/log_file_viewer.dart b/auth/lib/ui/tools/debug/log_file_viewer.dart
index cc9321aa4..2a85969ed 100644
--- a/auth/lib/ui/tools/debug/log_file_viewer.dart
+++ b/auth/lib/ui/tools/debug/log_file_viewer.dart
@@ -2,11 +2,12 @@ import 'dart:io';
import 'dart:ui';
import 'package:ente_auth/ui/common/loading_widget.dart';
+import 'package:ente_auth/utils/platform_util.dart';
import 'package:flutter/material.dart';
class LogFileViewer extends StatefulWidget {
final File file;
- const LogFileViewer(this.file, {Key? key}) : super(key: key);
+ const LogFileViewer(this.file, {super.key});
@override
State createState() => _LogFileViewerState();
@@ -42,13 +43,17 @@ class _LogFileViewerState extends State {
return Container(
padding: const EdgeInsets.only(left: 12, top: 8, right: 12),
child: SingleChildScrollView(
- child: Text(
- _logs!,
- style: const TextStyle(
- fontFeatures: [
- FontFeature.tabularFigures(),
- ],
- height: 1.2,
+ child: SelectableRegion(
+ focusNode: FocusNode(),
+ selectionControls: PlatformUtil.selectionControls,
+ child: Text(
+ _logs!,
+ style: const TextStyle(
+ fontFeatures: [
+ FontFeature.tabularFigures(),
+ ],
+ height: 1.2,
+ ),
),
),
),
diff --git a/auth/lib/ui/tools/lock_screen.dart b/auth/lib/ui/tools/lock_screen.dart
index 213e7a99a..712aa233b 100644
--- a/auth/lib/ui/tools/lock_screen.dart
+++ b/auth/lib/ui/tools/lock_screen.dart
@@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
class LockScreen extends StatefulWidget {
- const LockScreen({Key? key}) : super(key: key);
+ const LockScreen({super.key});
@override
State createState() => _LockScreenState();
diff --git a/auth/lib/ui/two_factor_authentication_page.dart b/auth/lib/ui/two_factor_authentication_page.dart
index 6287abdf8..a83a66baa 100644
--- a/auth/lib/ui/two_factor_authentication_page.dart
+++ b/auth/lib/ui/two_factor_authentication_page.dart
@@ -3,13 +3,13 @@ import 'package:ente_auth/services/user_service.dart';
import 'package:ente_auth/ui/lifecycle_event_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:pinput/pin_put/pin_put.dart';
+
+import 'package:pinput/pinput.dart';
class TwoFactorAuthenticationPage extends StatefulWidget {
final String sessionID;
- const TwoFactorAuthenticationPage(this.sessionID, {Key? key})
- : super(key: key);
+ const TwoFactorAuthenticationPage(this.sessionID, {super.key});
@override
State createState() =>
@@ -85,29 +85,31 @@ class _TwoFactorAuthenticationPageState
const Padding(padding: EdgeInsets.all(32)),
Padding(
padding: const EdgeInsets.fromLTRB(40, 0, 40, 0),
- child: PinPut(
- fieldsCount: 6,
- onSubmit: (String code) {
+ child: Pinput(
+ onSubmitted: (String code) {
_verifyTwoFactorCode(code);
},
+ length: 6,
+ defaultPinTheme: const PinTheme(),
+ submittedPinTheme: PinTheme(
+ decoration: pinPutDecoration.copyWith(
+ borderRadius: BorderRadius.circular(20.0),
+ ),
+ ),
+ focusedPinTheme: PinTheme(
+ decoration: pinPutDecoration,
+ ),
+ followingPinTheme: PinTheme(
+ decoration: pinPutDecoration.copyWith(
+ borderRadius: BorderRadius.circular(5.0),
+ ),
+ ),
onChanged: (String pin) {
setState(() {
_code = pin;
});
},
controller: _pinController,
- submittedFieldDecoration: pinPutDecoration.copyWith(
- borderRadius: BorderRadius.circular(20.0),
- ),
- selectedFieldDecoration: pinPutDecoration,
- followingFieldDecoration: pinPutDecoration.copyWith(
- borderRadius: BorderRadius.circular(5.0),
- ),
- inputDecoration: const InputDecoration(
- focusedBorder: InputBorder.none,
- border: InputBorder.none,
- counterText: '',
- ),
autofocus: true,
),
),
diff --git a/auth/lib/ui/two_factor_recovery_page.dart b/auth/lib/ui/two_factor_recovery_page.dart
index e007a9c40..c8a8b1aa6 100644
--- a/auth/lib/ui/two_factor_recovery_page.dart
+++ b/auth/lib/ui/two_factor_recovery_page.dart
@@ -14,8 +14,8 @@ class TwoFactorRecoveryPage extends StatefulWidget {
this.sessionID,
this.encryptedSecret,
this.secretDecryptionNonce, {
- Key? key,
- }) : super(key: key);
+ super.key,
+ });
@override
State createState() => _TwoFactorRecoveryPageState();
diff --git a/auth/lib/ui/utils/icon_utils.dart b/auth/lib/ui/utils/icon_utils.dart
index 298a14333..771303952 100644
--- a/auth/lib/ui/utils/icon_utils.dart
+++ b/auth/lib/ui/utils/icon_utils.dart
@@ -87,7 +87,7 @@ class IconUtils {
Color? _getAdaptiveColor(String? hexColor, BuildContext context) {
if (hexColor == null) return null;
final theme = Theme.of(context).brightness;
- final color = Color(int.parse("0xFF" + hexColor));
+ final color = Color(int.parse("0xFF$hexColor"));
// Color is close to neutral-grey and it's too light or dark for theme
if (_isCloseToNeutralGrey(color) &&
((theme == Brightness.light && _getColorLuminance(color) > 0.70) ||
diff --git a/auth/lib/utils/auth_util.dart b/auth/lib/utils/auth_util.dart
index 0a6320191..b543b4c73 100644
--- a/auth/lib/utils/auth_util.dart
+++ b/auth/lib/utils/auth_util.dart
@@ -1,5 +1,8 @@
+import 'dart:io';
+
import 'package:ente_auth/l10n/l10n.dart';
import 'package:flutter/cupertino.dart';
+import 'package:flutter_local_authentication/flutter_local_authentication.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth_android/local_auth_android.dart';
import 'package:local_auth_ios/types/auth_messages_ios.dart';
@@ -7,32 +10,36 @@ import 'package:logging/logging.dart';
Future requestAuthentication(BuildContext context, String reason) async {
Logger("AuthUtil").info("Requesting authentication");
- await LocalAuthentication().stopAuthentication();
- final l10n = context.l10n;
- return await LocalAuthentication().authenticate(
- localizedReason: reason,
- authMessages: [
- AndroidAuthMessages(
- biometricHint: l10n.androidBiometricHint,
- biometricNotRecognized: l10n.androidBiometricNotRecognized,
- biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
- biometricSuccess: l10n.androidBiometricSuccess,
- cancelButton: l10n.androidCancelButton,
- deviceCredentialsRequiredTitle:
- l10n.androidDeviceCredentialsRequiredTitle,
- deviceCredentialsSetupDescription:
- l10n.androidDeviceCredentialsSetupDescription,
- goToSettingsButton: l10n.goToSettings,
- goToSettingsDescription: l10n.androidGoToSettingsDescription,
- signInTitle: l10n.androidSignInTitle,
- ),
- IOSAuthMessages(
- goToSettingsButton: l10n.goToSettings,
- goToSettingsDescription: l10n.goToSettings,
- lockOut: l10n.iOSLockOut,
- // cancelButton default value is "Ok"
- cancelButton: l10n.iOSOkButton,
- ),
- ],
- );
+ if (Platform.isMacOS || Platform.isLinux) {
+ return await FlutterLocalAuthentication().authenticate();
+ } else {
+ await LocalAuthentication().stopAuthentication();
+ final l10n = context.l10n;
+ return await LocalAuthentication().authenticate(
+ localizedReason: reason,
+ authMessages: [
+ AndroidAuthMessages(
+ biometricHint: l10n.androidBiometricHint,
+ biometricNotRecognized: l10n.androidBiometricNotRecognized,
+ biometricRequiredTitle: l10n.androidBiometricRequiredTitle,
+ biometricSuccess: l10n.androidBiometricSuccess,
+ cancelButton: l10n.androidCancelButton,
+ deviceCredentialsRequiredTitle:
+ l10n.androidDeviceCredentialsRequiredTitle,
+ deviceCredentialsSetupDescription:
+ l10n.androidDeviceCredentialsSetupDescription,
+ goToSettingsButton: l10n.goToSettings,
+ goToSettingsDescription: l10n.androidGoToSettingsDescription,
+ signInTitle: l10n.androidSignInTitle,
+ ),
+ IOSAuthMessages(
+ goToSettingsButton: l10n.goToSettings,
+ goToSettingsDescription: l10n.goToSettings,
+ lockOut: l10n.iOSLockOut,
+ // cancelButton default value is "Ok"
+ cancelButton: l10n.iOSOkButton,
+ ),
+ ],
+ );
+ }
}
diff --git a/auth/lib/utils/crypto_util.dart b/auth/lib/utils/crypto_util.dart
deleted file mode 100644
index 494ef130f..000000000
--- a/auth/lib/utils/crypto_util.dart
+++ /dev/null
@@ -1,509 +0,0 @@
-import 'dart:convert';
-import 'dart:io' as io;
-import 'dart:typed_data';
-
-import 'package:computer/computer.dart';
-import 'package:ente_auth/core/errors.dart';
-import 'package:ente_auth/models/derived_key_result.dart';
-import 'package:ente_auth/models/encryption_result.dart';
-import 'package:ente_auth/utils/device_info.dart';
-import 'package:flutter_sodium/flutter_sodium.dart';
-import 'package:logging/logging.dart';
-
-const int encryptionChunkSize = 4 * 1024 * 1024;
-final int decryptionChunkSize =
- encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes;
-const int hashChunkSize = 4 * 1024 * 1024;
-const int loginSubKeyLen = 32;
-const int loginSubKeyId = 1;
-const String loginSubKeyContext = "loginctx";
-
-Uint8List cryptoSecretboxEasy(Map args) {
- return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]);
-}
-
-Uint8List cryptoSecretboxOpenEasy(Map args) {
- return Sodium.cryptoSecretboxOpenEasy(
- args["cipher"],
- args["nonce"],
- args["key"],
- );
-}
-
-Uint8List cryptoPwHash(Map args) {
- return Sodium.cryptoPwhash(
- Sodium.cryptoSecretboxKeybytes,
- args["password"],
- args["salt"],
- args["opsLimit"],
- args["memLimit"],
- Sodium.cryptoPwhashAlgArgon2id13,
- );
-}
-
-Uint8List cryptoKdfDeriveFromKey(
- Map args,
-) {
- return Sodium.cryptoKdfDeriveFromKey(
- args["subkeyLen"],
- args["subkeyId"],
- args["context"],
- args["key"],
- );
-}
-
-// Returns the hash for a given file, chunking it in batches of hashChunkSize
-Future cryptoGenericHash(Map args) async {
- final sourceFile = io.File(args["sourceFilePath"]);
- final sourceFileLength = await sourceFile.length();
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final state =
- Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax);
- var bytesRead = 0;
- bool isDone = false;
- while (!isDone) {
- var chunkSize = hashChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- isDone = true;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- Sodium.cryptoGenerichashUpdate(state, buffer);
- }
- await inputFile.close();
- return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax);
-}
-
-EncryptionResult chachaEncryptData(Map args) {
- final initPushResult =
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
- final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
- initPushResult.state,
- args["source"],
- null,
- Sodium.cryptoSecretstreamXchacha20poly1305TagFinal,
- );
- return EncryptionResult(
- encryptedData: encryptedData,
- header: initPushResult.header,
- );
-}
-
-// Encrypts a given file, in chunks of encryptionChunkSize
-Future chachaEncryptFile(Map args) async {
- final encryptionStartTime = DateTime.now().millisecondsSinceEpoch;
- final logger = Logger("ChaChaEncrypt");
- final sourceFile = io.File(args["sourceFilePath"]);
- final destinationFile = io.File(args["destinationFilePath"]);
- final sourceFileLength = await sourceFile.length();
- logger.info("Encrypting file of size " + sourceFileLength.toString());
-
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
- final initPushResult =
- Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
- var bytesRead = 0;
- var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
- while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
- var chunkSize = encryptionChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
- initPushResult.state,
- buffer,
- null,
- tag,
- );
- await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append);
- }
- await inputFile.close();
-
- logger.info(
- "Encryption time: " +
- (DateTime.now().millisecondsSinceEpoch - encryptionStartTime)
- .toString(),
- );
-
- return EncryptionResult(key: key, header: initPushResult.header);
-}
-
-Future chachaDecryptFile(Map args) async {
- final logger = Logger("ChaChaDecrypt");
- final decryptionStartTime = DateTime.now().millisecondsSinceEpoch;
- final sourceFile = io.File(args["sourceFilePath"]);
- final destinationFile = io.File(args["destinationFilePath"]);
- final sourceFileLength = await sourceFile.length();
- logger.info("Decrypting file of size " + sourceFileLength.toString());
-
- final inputFile = sourceFile.openSync(mode: io.FileMode.read);
- final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
- args["header"],
- args["key"],
- );
-
- var bytesRead = 0;
- var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
- while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
- var chunkSize = decryptionChunkSize;
- if (bytesRead + chunkSize >= sourceFileLength) {
- chunkSize = sourceFileLength - bytesRead;
- }
- final buffer = await inputFile.read(chunkSize);
- bytesRead += chunkSize;
- final pullResult =
- Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
- await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append);
- tag = pullResult.tag;
- }
- inputFile.closeSync();
-
- logger.info(
- "ChaCha20 Decryption time: " +
- (DateTime.now().millisecondsSinceEpoch - decryptionStartTime)
- .toString(),
- );
-}
-
-Uint8List chachaDecryptData(Map args) {
- final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull(
- args["header"],
- args["key"],
- );
- final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull(
- pullState,
- args["source"],
- null,
- );
- return pullResult.m;
-}
-
-class CryptoUtil {
- // Note: workers are turned on during app startup.
- static final Computer _computer = Computer.shared();
-
- static init() {
- Sodium.init();
- }
-
- static Uint8List base642bin(
- String b64, {
- String? ignore,
- int variant = Sodium.base64VariantOriginal,
- }) {
- return Sodium.base642bin(b64, ignore: ignore, variant: variant);
- }
-
- static String bin2base64(
- Uint8List bin, {
- bool urlSafe = false,
- }) {
- return Sodium.bin2base64(
- bin,
- variant:
- urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
- );
- }
-
- static String bin2hex(Uint8List bin) {
- return Sodium.bin2hex(bin);
- }
-
- static Uint8List hex2bin(String hex) {
- return Sodium.hex2bin(hex);
- }
-
- // Encrypts the given source, with the given key and a randomly generated
- // nonce, using XSalsa20 (w Poly1305 MAC).
- // This function runs on the same thread as the caller, so should be used only
- // for small amounts of data where thread switching can result in a degraded
- // user experience
- static EncryptionResult encryptSync(Uint8List source, Uint8List key) {
- final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes);
-
- final args = {};
- args["source"] = source;
- args["nonce"] = nonce;
- args["key"] = key;
- final encryptedData = cryptoSecretboxEasy(args);
- return EncryptionResult(
- key: key,
- nonce: nonce,
- encryptedData: encryptedData,
- );
- }
-
- // Decrypts the given cipher, with the given key and nonce using XSalsa20
- // (w Poly1305 MAC).
- static Future decrypt(
- Uint8List cipher,
- Uint8List key,
- Uint8List nonce,
- ) async {
- final args = {};
- args["cipher"] = cipher;
- args["nonce"] = nonce;
- args["key"] = key;
- return _computer.compute(
- cryptoSecretboxOpenEasy,
- param: args,
- taskName: "decrypt",
- );
- }
-
- // Decrypts the given cipher, with the given key and nonce using XSalsa20
- // (w Poly1305 MAC).
- // This function runs on the same thread as the caller, so should be used only
- // for small amounts of data where thread switching can result in a degraded
- // user experience
- static Uint8List decryptSync(
- Uint8List cipher,
- Uint8List key,
- Uint8List nonce,
- ) {
- final args = {};
- args["cipher"] = cipher;
- args["nonce"] = nonce;
- args["key"] = key;
- return cryptoSecretboxOpenEasy(args);
- }
-
- // Encrypts the given source, with the given key and a randomly generated
- // nonce, using XChaCha20 (w Poly1305 MAC).
- // This function runs on the isolate pool held by `_computer`.
- // TODO: Remove "ChaCha", an implementation detail from the function name
- static Future encryptChaCha(
- Uint8List source,
- Uint8List key,
- ) async {
- final args = {};
- args["source"] = source;
- args["key"] = key;
- return _computer.compute(
- chachaEncryptData,
- param: args,
- taskName: "encryptChaCha",
- );
- }
-
- // Decrypts the given source, with the given key and header using XChaCha20
- // (w Poly1305 MAC).
- // TODO: Remove "ChaCha", an implementation detail from the function name
- static Future decryptChaCha(
- Uint8List source,
- Uint8List key,
- Uint8List header,
- ) async {
- final args = {};
- args["source"] = source;
- args["key"] = key;
- args["header"] = header;
- return _computer.compute(
- chachaDecryptData,
- param: args,
- taskName: "decryptChaCha",
- );
- }
-
- // Encrypts the file at sourceFilePath, with the key (if provided) and a
- // randomly generated nonce using XChaCha20 (w Poly1305 MAC), and writes it
- // to the destinationFilePath.
- // If a key is not provided, one is generated and returned.
- static Future encryptFile(
- String sourceFilePath,
- String destinationFilePath, {
- Uint8List? key,
- }) {
- final args = {};
- args["sourceFilePath"] = sourceFilePath;
- args["destinationFilePath"] = destinationFilePath;
- args["key"] = key;
- return _computer.compute(
- chachaEncryptFile,
- param: args,
- taskName: "encryptFile",
- );
- }
-
- // Decrypts the file at sourceFilePath, with the given key and header using
- // XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
- static Future decryptFile(
- String sourceFilePath,
- String destinationFilePath,
- Uint8List header,
- Uint8List key,
- ) {
- final args = {};
- args["sourceFilePath"] = sourceFilePath;
- args["destinationFilePath"] = destinationFilePath;
- args["header"] = header;
- args["key"] = key;
- return _computer.compute(
- chachaDecryptFile,
- param: args,
- taskName: "decryptFile",
- );
- }
-
- // Generates and returns a 256-bit key.
- static Uint8List generateKey() {
- return Sodium.cryptoSecretboxKeygen();
- }
-
- // Generates and returns a random byte buffer of length
- // crypto_pwhash_SALTBYTES (16)
- static Uint8List getSaltToDeriveKey() {
- return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes);
- }
-
- // Generates and returns a secret key and the corresponding public key.
- static Future generateKeyPair() async {
- return Sodium.cryptoBoxKeypair();
- }
-
- // Decrypts the input using the given publicKey-secretKey pair
- static Uint8List openSealSync(
- Uint8List input,
- Uint8List publicKey,
- Uint8List secretKey,
- ) {
- return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
- }
-
- // Encrypts the input using the given publicKey
- static Uint8List sealSync(Uint8List input, Uint8List publicKey) {
- return Sodium.cryptoBoxSeal(input, publicKey);
- }
-
- // Derives a key for a given password and salt using Argon2id, v1.3.
- // The function first attempts to derive a key with both memLimit and opsLimit
- // set to their Sensitive variants.
- // If this fails, say on a device with insufficient RAM, we retry by halving
- // the memLimit and doubling the opsLimit, while ensuring that we stay within
- // the min and max limits for both parameters.
- // At all points, we ensure that the product of these two variables (the area
- // under the graph that determines the amount of work required) is a constant.
- static Future deriveSensitiveKey(
- Uint8List password,
- Uint8List salt,
- ) async {
- final logger = Logger("pwhash");
- int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
- int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
- if (await isLowSpecDevice()) {
- logger.info("low spec device detected");
- // When sensitive memLimit (1 GB) is used, on low spec device the OS might
- // kill the app with OOM. To avoid that, start with 256 MB and
- // corresponding ops limit (16).
- // This ensures that the product of these two variables
- // (the area under the graph that determines the amount of work required)
- // stays the same
- // SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: 1073741824
- // SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE: 268435456
- // SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE: 4
- memLimit = Sodium.cryptoPwhashMemlimitModerate;
- final factor = Sodium.cryptoPwhashMemlimitSensitive ~/
- Sodium.cryptoPwhashMemlimitModerate; // = 4
- opsLimit = opsLimit * factor; // = 16
- }
- Uint8List key;
- while (memLimit >= Sodium.cryptoPwhashMemlimitMin &&
- opsLimit <= Sodium.cryptoPwhashOpslimitMax) {
- try {
- key = await deriveKey(password, salt, memLimit, opsLimit);
- return DerivedKeyResult(key, memLimit, opsLimit);
- } catch (e, s) {
- logger.warning(
- "failed to deriveKey mem: $memLimit, ops: $opsLimit",
- e,
- s,
- );
- }
- memLimit = (memLimit / 2).round();
- opsLimit = opsLimit * 2;
- }
- throw UnsupportedError("Cannot perform this operation on this device");
- }
-
- // Derives a key for the given password and salt, using Argon2id, v1.3
- // with memory and ops limit hardcoded to their Interactive variants
- // NOTE: This is only used while setting passwords for shared links, as an
- // extra layer of authentication (atop the access token and collection key).
- // More details @ https://ente.io/blog/building-shareable-links/
- static Future deriveInteractiveKey(
- Uint8List password,
- Uint8List salt,
- ) async {
- final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
- final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
- final key = await deriveKey(password, salt, memLimit, opsLimit);
- return DerivedKeyResult(key, memLimit, opsLimit);
- }
-
- // Derives a key for a given password, salt, memLimit and opsLimit using
- // Argon2id, v1.3.
- static Future deriveKey(
- Uint8List password,
- Uint8List salt,
- int memLimit,
- int opsLimit,
- ) async {
- try {
- return await _computer.compute(
- cryptoPwHash,
- param: {
- "password": password,
- "salt": salt,
- "memLimit": memLimit,
- "opsLimit": opsLimit,
- },
- taskName: "deriveKey",
- );
- } catch (e, s) {
- final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
- 'opsLimit: $opsLimit';
- Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
- throw KeyDerivationError();
- }
- }
-
- // derives a Login key as subKey from the given key by applying KDF
- // (Key Derivation Function) with the `loginSubKeyId` and
- // `loginSubKeyLen` and `loginSubKeyContext` as context
- static Future deriveLoginKey(
- Uint8List key,
- ) async {
- try {
- final Uint8List derivedKey = await _computer.compute(
- cryptoKdfDeriveFromKey,
- param: {
- "key": key,
- "subkeyId": loginSubKeyId,
- "subkeyLen": loginSubKeyLen,
- "context": utf8.encode(loginSubKeyContext),
- },
- taskName: "deriveLoginKey",
- );
- // return the first 16 bytes of the derived key
- return derivedKey.sublist(0, 16);
- } catch (e, s) {
- Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s);
- throw LoginKeyDerivationError();
- }
- }
-
- // Computes and returns the hash of the source file
- static Future getHash(io.File source) {
- return _computer.compute(
- cryptoGenericHash,
- param: {
- "sourceFilePath": source.path,
- },
- taskName: "fileHash",
- );
- }
-}
diff --git a/auth/lib/utils/data_util.dart b/auth/lib/utils/data_util.dart
deleted file mode 100644
index 3dcae58fa..000000000
--- a/auth/lib/utils/data_util.dart
+++ /dev/null
@@ -1,26 +0,0 @@
-import 'dart:math';
-
-double convertBytesToGBs(final int bytes, {int precision = 2}) {
- return double.parse(
- (bytes / (1024 * 1024 * 1024)).toStringAsFixed(precision),
- );
-}
-
-final storageUnits = ["bytes", "KB", "MB", "GB"];
-
-String convertBytesToReadableFormat(int bytes) {
- int storageUnitIndex = 0;
- while (bytes >= 1024 && storageUnitIndex < storageUnits.length - 1) {
- storageUnitIndex++;
- bytes = (bytes / 1024).round();
- }
- return bytes.toString() + " " + storageUnits[storageUnitIndex];
-}
-
-String formatBytes(int bytes, [int decimals = 2]) {
- if (bytes == 0) return '0 bytes';
- const k = 1024;
- final int dm = decimals < 0 ? 0 : decimals;
- final int i = (log(bytes) / log(k)).floor();
- return ((bytes / pow(k, i)).toStringAsFixed(dm)) + ' ' + storageUnits[i];
-}
diff --git a/auth/lib/utils/device_info.dart b/auth/lib/utils/device_info.dart
deleted file mode 100644
index 5832ec6de..000000000
--- a/auth/lib/utils/device_info.dart
+++ /dev/null
@@ -1,44 +0,0 @@
-import 'dart:io';
-
-import 'package:device_info_plus/device_info_plus.dart';
-import 'package:flutter/foundation.dart';
-import 'package:logging/logging.dart';
-
-late DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
-
-// https://gist.github.com/adamawolf/3048717
-late Set iOSLowEndMachineCodes = {
- "iPhone5,1", //iPhone 5 (GSM)
- "iPhone5,2", //iPhone 5 (GSM+CDMA)
- "iPhone5,3", //iPhone 5C (GSM)
- "iPhone5,4", //iPhone 5C (Global)
- "iPhone6,1", //iPhone 5S (GSM)
- "iPhone6,2", //iPhone 5S (Global)
- "iPhone7,1", //iPhone 6 Plus
- "iPhone7,2", //iPhone 6
- "iPhone8,1", // iPhone 6s
- "iPhone8,2", // iPhone 6s Plus
- "iPhone8,4", // iPhone SE (GSM)
- "iPhone9,1", // iPhone 7
- "iPhone9,2", // iPhone 7 Plus
- "iPhone9,3", // iPhone 7
- "iPhone9,4", // iPhone 7 Plus
- "iPhone10,1", // iPhone 8
- "iPhone10,2", // iPhone 8 Plus
- "iPhone10,3", // iPhone X Global
- "iPhone10,4", // iPhone 8
- "iPhone10,5", // iPhone 8
-};
-
-Future isLowSpecDevice() async {
- try {
- if (Platform.isIOS) {
- final IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo;
- debugPrint("ios utc name ${iosInfo.utsname.machine}");
- return iOSLowEndMachineCodes.contains(iosInfo.utsname.machine);
- }
- } catch (e) {
- Logger("device_info").severe("deviceSpec check failed", e);
- }
- return false;
-}
diff --git a/auth/lib/utils/dialog_util.dart b/auth/lib/utils/dialog_util.dart
index 9cf7273fd..d24608b78 100644
--- a/auth/lib/utils/dialog_util.dart
+++ b/auth/lib/utils/dialog_util.dart
@@ -47,11 +47,11 @@ Future showErrorDialogForException({
String apiErrorPrefix = "It looks like something went wrong.",
}) async {
String errorMessage = context.l10n.tempErrorContactSupportIfPersists;
- if (exception is DioError &&
+ if (exception is DioException &&
exception.response != null &&
exception.response!.data["code"] != null) {
errorMessage =
- "$apiErrorPrefix\n\nReason: " + exception.response!.data["code"];
+ "$apiErrorPrefix\n\nReason: ${exception.response!.data["code"]}";
}
return showDialogWidget(
context: context,
diff --git a/auth/lib/utils/directory_utils.dart b/auth/lib/utils/directory_utils.dart
new file mode 100644
index 000000000..b9da6e613
--- /dev/null
+++ b/auth/lib/utils/directory_utils.dart
@@ -0,0 +1,12 @@
+import 'package:path/path.dart' as p;
+import 'package:path_provider/path_provider.dart';
+
+class DirectoryUtils {
+ static Future getDatabasePath(String databaseName) async => p.joinAll(
+ [
+ (await getApplicationDocumentsDirectory()).path,
+ "ente",
+ ".$databaseName",
+ ],
+ );
+}
diff --git a/auth/lib/utils/email_util.dart b/auth/lib/utils/email_util.dart
index 0da675f21..dce1c7aef 100644
--- a/auth/lib/utils/email_util.dart
+++ b/auth/lib/utils/email_util.dart
@@ -4,22 +4,21 @@ import 'package:archive/archive_io.dart';
import 'package:email_validator/email_validator.dart';
import 'package:ente_auth/core/configuration.dart';
import 'package:ente_auth/core/logging/super_logging.dart';
-import 'package:ente_auth/ente_theme_data.dart';
import 'package:ente_auth/l10n/l10n.dart';
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
import 'package:ente_auth/ui/components/dialog_widget.dart';
import 'package:ente_auth/ui/components/models/button_type.dart';
import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart';
-// import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart';
import 'package:ente_auth/utils/dialog_util.dart';
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:ente_auth/utils/share_utils.dart';
import 'package:ente_auth/utils/toast_util.dart';
-import 'package:file_saver/file_saver.dart';
+import "package:file_saver/file_saver.dart";
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_email_sender/flutter_email_sender.dart';
-import 'package:intl/intl.dart';
+import "package:intl/intl.dart";
import 'package:logging/logging.dart';
-// import 'package:open_mail_app/open_mail_app.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
@@ -27,7 +26,10 @@ import 'package:url_launcher/url_launcher.dart';
final Logger _logger = Logger('email_util');
-bool isValidEmail(String email) {
+bool isValidEmail(String? email) {
+ if (email == null) {
+ return false;
+ }
return EmailValidator.validate(email);
}
@@ -40,90 +42,73 @@ Future sendLogs(
String? body,
}) async {
final l10n = context.l10n;
- final List actions = [
- TextButton(
- child: Row(
- mainAxisAlignment: MainAxisAlignment.start,
- children: [
- Icon(
- Icons.feed_outlined,
- color: Theme.of(context).iconTheme.color?.withOpacity(0.85),
- ),
- const Padding(padding: EdgeInsets.all(4)),
- Text(
- l10n.viewLogsAction,
- style: TextStyle(
- color: Theme.of(context)
- .colorScheme
- .defaultTextColor
- .withOpacity(0.85),
+ showDialogWidget(
+ context: context,
+ title: title,
+ icon: Icons.bug_report_outlined,
+ body: l10n.sendLogsDescription,
+ buttons: [
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.neutral,
+ labelText: l10n.reportABug,
+ buttonAction: ButtonAction.first,
+ shouldSurfaceExecutionStates: false,
+ onTap: () async {
+ await _sendLogs(context, toEmail, subject, body);
+ if (postShare != null) {
+ postShare();
+ }
+ },
+ ),
+ //isInAlert is false here as we don't want to the dialog to dismiss
+ //on pressing this button
+ ButtonWidget(
+ buttonType: ButtonType.secondary,
+ labelText: l10n.viewLogsAction,
+ buttonAction: ButtonAction.second,
+ onTap: () async {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return LogFileViewer(SuperLogging.logFile!);
+ },
+ barrierColor: Colors.black87,
+ barrierDismissible: false,
+ );
+ },
+ ),
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.secondary,
+ labelText: l10n.exportLogs,
+ buttonAction: ButtonAction.third,
+ onTap: () async {
+ Future.delayed(
+ const Duration(milliseconds: 200),
+ () => shareDialog(
+ context,
+ title,
+ saveAction: () async {
+ final zipFilePath = await getZippedLogsFile(context);
+ await exportLogs(context, zipFilePath);
+ },
+ sendAction: () async {
+ final zipFilePath = await getZippedLogsFile(context);
+ await exportLogs(context, zipFilePath, true);
+ },
),
- ),
- ],
+ );
+ },
),
- onPressed: () async {
- showDialog(
- context: context,
- builder: (BuildContext context) {
- return LogFileViewer(SuperLogging.logFile!);
- },
- barrierColor: Colors.black87,
- barrierDismissible: false,
- );
- },
- ),
- TextButton(
- child: Text(
- title,
- style: TextStyle(
- color: Theme.of(context).colorScheme.alternativeColor,
- ),
- ),
- onPressed: () async {
- Navigator.of(context, rootNavigator: true).pop('dialog');
- await _sendLogs(context, toEmail, subject, body);
- if (postShare != null) {
- postShare();
- }
- },
- ),
- ];
- final List content = [];
- content.addAll(
- [
- Text(
- l10n.sendLogsDescription,
- style: const TextStyle(
- height: 1.5,
- fontSize: 16,
- ),
- ),
- const Padding(padding: EdgeInsets.all(12)),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: actions,
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.secondary,
+ labelText: l10n.cancel,
+ buttonAction: ButtonAction.cancel,
),
],
);
- final confirmation = AlertDialog(
- title: Text(
- title,
- style: const TextStyle(
- fontSize: 18,
- ),
- ),
- content: SingleChildScrollView(
- child: ListBody(
- children: content,
- ),
- ),
- );
- showDialog(
- context: context,
- builder: (_) {
- return confirmation;
- },
- );
}
Future _sendLogs(
@@ -144,6 +129,7 @@ Future _sendLogs(
await FlutterEmailSender.send(email);
} catch (e, s) {
_logger.severe('email sender failed', e, s);
+ Navigator.of(context, rootNavigator: true).pop();
await shareLogs(context, toEmail, zipFilePath);
}
}
@@ -153,13 +139,13 @@ Future getZippedLogsFile(BuildContext context) async {
final dialog = createProgressDialog(context, l10n.preparingLogsTitle);
await dialog.show();
final logsPath = (await getApplicationSupportDirectory()).path;
- final logsDirectory = Directory(logsPath + "/logs");
+ final logsDirectory = Directory("$logsPath/logs");
final tempPath = (await getTemporaryDirectory()).path;
final zipFilePath =
- tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip";
+ "$tempPath/logs-${Configuration.instance.getUserID() ?? 0}.zip";
final encoder = ZipFileEncoder();
encoder.create(zipFilePath);
- encoder.addDirectory(logsDirectory);
+ await encoder.addDirectory(logsDirectory);
encoder.close();
await dialog.hide();
return zipFilePath;
@@ -170,14 +156,15 @@ Future shareLogs(
String toEmail,
String zipFilePath,
) async {
+ final l10n = context.l10n;
final result = await showDialogWidget(
context: context,
- title: context.l10n.emailYourLogs,
- body: context.l10n.pleaseSendTheLogsTo(toEmail),
+ title: l10n.emailYourLogs,
+ body: l10n.pleaseSendTheLogsTo(toEmail),
buttons: [
ButtonWidget(
buttonType: ButtonType.neutral,
- labelText: context.l10n.copyEmailAddress,
+ labelText: l10n.copyEmailAddress,
isInAlert: true,
buttonAction: ButtonAction.first,
onTap: () async {
@@ -187,13 +174,13 @@ Future shareLogs(
),
ButtonWidget(
buttonType: ButtonType.neutral,
- labelText: context.l10n.exportLogs,
+ labelText: l10n.exportLogs,
isInAlert: true,
buttonAction: ButtonAction.second,
),
ButtonWidget(
buttonType: ButtonType.secondary,
- labelText: context.l10n.cancel,
+ labelText: l10n.cancel,
isInAlert: true,
buttonAction: ButtonAction.cancel,
),
@@ -204,18 +191,24 @@ Future shareLogs(
}
}
-Future exportLogs(BuildContext context, String zipFilePath) async {
+Future exportLogs(
+ BuildContext context,
+ String zipFilePath, [
+ bool isSharing = false,
+]) async {
final Size size = MediaQuery.of(context).size;
- if (Platform.isAndroid) {
- DateTime now = DateTime.now().toUtc();
- String shortMonthName = DateFormat('MMM').format(now); // Short month name
- String logFileName =
+ if (!isSharing) {
+ final DateTime now = DateTime.now().toUtc();
+ final String shortMonthName = DateFormat('MMM').format(now); // Short month
+ final String logFileName =
'ente-logs-${now.year}-$shortMonthName-${now.day}-${now.hour}-${now.minute}';
- await FileSaver.instance.saveAs(
- name: logFileName,
- filePath: zipFilePath,
- mimeType: MimeType.zip,
- ext: 'zip',
+
+ final bytes = await File(zipFilePath).readAsBytes();
+ await PlatformUtil.shareFile(
+ logFileName,
+ 'zip',
+ bytes,
+ MimeType.zip,
);
} else {
await Share.shareXFiles(
@@ -233,8 +226,8 @@ Future sendEmail(
}) async {
try {
final String clientDebugInfo = await _clientInfo();
- final String _subject = subject ?? '[Support]';
- final String _body = (body ?? '') + clientDebugInfo;
+ final String subject0 = subject ?? '[Support]';
+ final String body0 = (body ?? '') + clientDebugInfo;
// final EmailContent email = EmailContent(
// to: [
// to,
@@ -248,7 +241,7 @@ Future sendEmail(
final Uri params = Uri(
scheme: 'mailto',
path: to,
- query: 'subject=$_subject&body=$_body',
+ query: 'subject=$subject0&body=$body0',
);
if (await canLaunchUrl(params)) {
await launchUrl(params);
@@ -278,27 +271,15 @@ Future _clientInfo() async {
void _showNoMailAppsDialog(BuildContext context, String toEmail) {
final l10n = context.l10n;
- showDialog(
- context: context,
- builder: (context) {
- return AlertDialog(
- title: Text(l10n.emailUsMessage(toEmail)),
- actions: [
- TextButton(
- child: Text(l10n.copyEmailAction),
- onPressed: () async {
- await Clipboard.setData(ClipboardData(text: toEmail));
- showShortToast(context, l10n.copied);
- },
- ),
- TextButton(
- child: Text(l10n.ok),
- onPressed: () {
- Navigator.pop(context);
- },
- ),
- ],
- );
+ showChoiceDialog(
+ context,
+ icon: Icons.email_outlined,
+ title: l10n.emailUsMessage(toEmail),
+ firstButtonLabel: l10n.copyEmailAddress,
+ secondButtonLabel: l10n.ok,
+ firstButtonOnTap: () async {
+ await Clipboard.setData(ClipboardData(text: toEmail));
+ showShortToast(context, l10n.copied);
},
);
}
diff --git a/auth/lib/utils/navigation_util.dart b/auth/lib/utils/navigation_util.dart
index 38a3dbe53..58aa0fb85 100644
--- a/auth/lib/utils/navigation_util.dart
+++ b/auth/lib/utils/navigation_util.dart
@@ -109,9 +109,9 @@ class SwipeableRouteBuilder extends PageRoute {
class TransparentRoute extends PageRoute {
TransparentRoute({
required this.builder,
- RouteSettings? settings,
+ super.settings,
}) : assert(builder != null),
- super(settings: settings, fullscreenDialog: false);
+ super(fullscreenDialog: false);
final WidgetBuilder? builder;
diff --git a/auth/lib/utils/package_info_util.dart b/auth/lib/utils/package_info_util.dart
new file mode 100644
index 000000000..0bb5c2458
--- /dev/null
+++ b/auth/lib/utils/package_info_util.dart
@@ -0,0 +1,20 @@
+import 'package:ente_auth/utils/platform_util.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+
+class PackageInfoUtil {
+ Future getPackageInfo() async {
+ return await PackageInfo.fromPlatform();
+ }
+
+ String getVersion(PackageInfo info) {
+ return info.version;
+ }
+
+ String getPackageName(PackageInfo info) {
+ if (PlatformUtil.isMobile()) {
+ return info.packageName;
+ } else {
+ return 'io.ente.auth';
+ }
+ }
+}
diff --git a/auth/lib/utils/platform_util.dart b/auth/lib/utils/platform_util.dart
new file mode 100644
index 000000000..dc0245cb0
--- /dev/null
+++ b/auth/lib/utils/platform_util.dart
@@ -0,0 +1,93 @@
+import 'dart:io';
+
+import 'package:desktop_webview_window/desktop_webview_window.dart';
+import 'package:ente_auth/ui/common/web_page.dart';
+import 'package:file_saver/file_saver.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:url_launcher/url_launcher_string.dart';
+import 'package:window_manager/window_manager.dart';
+
+class PlatformUtil {
+ static bool isDesktop() {
+ return !kIsWeb &&
+ (Platform.isWindows || Platform.isLinux || Platform.isMacOS);
+ }
+
+ static bool isMobile() {
+ return !kIsWeb && (Platform.isAndroid || Platform.isIOS);
+ }
+
+ static bool isWeb() {
+ return kIsWeb;
+ }
+
+ static TextSelectionControls get selectionControls => Platform.isAndroid
+ ? materialTextSelectionControls
+ : Platform.isIOS
+ ? cupertinoTextSelectionControls
+ : desktopTextSelectionControls;
+
+ static openWebView(BuildContext context, String title, String url) async {
+ if (PlatformUtil.isDesktop()) {
+ if (!await WebviewWindow.isWebviewAvailable()) {
+ launchUrlString(url);
+ return;
+ }
+
+ final webview = await WebviewWindow.create(
+ configuration: CreateConfiguration(
+ title: title,
+ ),
+ );
+ webview.launch(url);
+ return;
+ }
+ Navigator.of(context).push(
+ MaterialPageRoute(
+ builder: (BuildContext context) {
+ return WebPage(
+ title,
+ url,
+ );
+ },
+ ),
+ );
+ }
+
+ static Future shareFile(
+ String fileName,
+ String extension,
+ Uint8List bytes,
+ MimeType type,
+ ) async {
+ try {
+ if (Platform.isAndroid || Platform.isIOS) {
+ await FileSaver.instance.saveAs(
+ name: fileName,
+ ext: extension,
+ bytes: bytes,
+ mimeType: type,
+ );
+ } else {
+ await FileSaver.instance.saveFile(
+ name: fileName,
+ ext: extension,
+ bytes: bytes,
+ mimeType: type,
+ );
+ }
+ } catch (e) {}
+ }
+
+ // Needed to fix issue with local_auth on Windows
+ // https://github.com/flutter/flutter/issues/122322
+ static Future refocusWindows() async {
+ if (!Platform.isWindows) return;
+ await windowManager.blur();
+ await windowManager.focus();
+ await windowManager.setAlwaysOnTop(true);
+ await windowManager.setAlwaysOnTop(false);
+ }
+}
diff --git a/auth/lib/utils/share_utils.dart b/auth/lib/utils/share_utils.dart
new file mode 100644
index 000000000..32e779ea3
--- /dev/null
+++ b/auth/lib/utils/share_utils.dart
@@ -0,0 +1,51 @@
+import 'dart:io';
+
+import 'package:ente_auth/l10n/l10n.dart';
+import 'package:ente_auth/ui/components/buttons/button_widget.dart';
+import 'package:ente_auth/ui/components/dialog_widget.dart';
+import 'package:ente_auth/ui/components/models/button_type.dart';
+import 'package:flutter/material.dart';
+
+Future shareDialog(
+ BuildContext context,
+ String title, {
+ required Function saveAction,
+ required Function sendAction,
+}) async {
+ final l10n = context.l10n;
+ showDialogWidget(
+ context: context,
+ title: title,
+ body: Platform.isLinux || Platform.isWindows
+ ? l10n.saveOnlyDescription
+ : l10n.saveOrSendDescription,
+ buttons: [
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.neutral,
+ labelText: l10n.save,
+ buttonAction: ButtonAction.first,
+ shouldSurfaceExecutionStates: false,
+ onTap: () async {
+ await saveAction();
+ },
+ ),
+ if (!Platform.isWindows && !Platform.isLinux)
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.secondary,
+ labelText: l10n.send,
+ buttonAction: ButtonAction.second,
+ onTap: () async {
+ await sendAction();
+ },
+ ),
+ ButtonWidget(
+ isInAlert: true,
+ buttonType: ButtonType.secondary,
+ labelText: l10n.cancel,
+ buttonAction: ButtonAction.cancel,
+ ),
+ ],
+ );
+}
diff --git a/auth/lib/utils/toast_util.dart b/auth/lib/utils/toast_util.dart
index a9f0392c2..319c0eb91 100644
--- a/auth/lib/utils/toast_util.dart
+++ b/auth/lib/utils/toast_util.dart
@@ -1,5 +1,6 @@
import 'package:ente_auth/ente_theme_data.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:fluttertoast/fluttertoast.dart';
Future showToast(
@@ -8,16 +9,42 @@ Future showToast(
toastLength = Toast.LENGTH_LONG,
iOSDismissOnTap = true,
}) async {
- await Fluttertoast.cancel();
- return Fluttertoast.showToast(
- msg: message,
- toastLength: toastLength,
- gravity: ToastGravity.BOTTOM,
- timeInSecForIosWeb: 1,
- backgroundColor: Theme.of(context).colorScheme.toastBackgroundColor,
- textColor: Theme.of(context).colorScheme.toastTextColor,
- fontSize: 16.0,
- );
+ try {
+ await Fluttertoast.cancel();
+ return Fluttertoast.showToast(
+ msg: message,
+ toastLength: toastLength,
+ gravity: ToastGravity.BOTTOM,
+ timeInSecForIosWeb: 1,
+ backgroundColor: Theme.of(context).colorScheme.toastBackgroundColor,
+ textColor: Theme.of(context).colorScheme.toastTextColor,
+ fontSize: 16.0,
+ );
+ } on MissingPluginException catch (_) {
+ Widget toast = Container(
+ padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(25.0),
+ color: Theme.of(context).colorScheme.toastBackgroundColor,
+ ),
+ child: Text(
+ message,
+ style: TextStyle(
+ color: Theme.of(context).colorScheme.toastTextColor,
+ fontSize: 16.0,
+ ),
+ ),
+ );
+
+ final fToast = FToast();
+ fToast.init(context);
+
+ fToast.showToast(
+ child: toast,
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ }
}
Future