feat: desktop (7124ed710acf33d895faa4730a04b87f9c5cac24)
This commit is contained in:
parent
ef553d9401
commit
b86729050a
166 changed files with 3120 additions and 2641 deletions
6
auth/.gitignore
vendored
6
auth/.gitignore
vendored
|
@ -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/
|
|
@ -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'
|
||||
|
|
25
auth/distribute_options.yaml
Normal file
25
auth/distribute_options.yaml
Normal file
|
@ -0,0 +1,25 @@
|
|||
output: dist/
|
||||
|
||||
releases:
|
||||
- name: dev
|
||||
jobs:
|
||||
- name: release-dev-linux-zip
|
||||
package:
|
||||
platform: linux
|
||||
target: zip
|
||||
- name: release-dev-linux-deb
|
||||
package:
|
||||
platform: linux
|
||||
target: deb
|
||||
- name: release-dev-linux-appimage
|
||||
package:
|
||||
platform: linux
|
||||
target: appimage
|
||||
- name: release-dev-windows-exe
|
||||
package:
|
||||
platform: windows
|
||||
target: exe
|
||||
- name: release-dev-macos-dmg
|
||||
package:
|
||||
platform: macos
|
||||
target: dmg
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import UIKit
|
||||
import Flutter
|
||||
import UIKit
|
||||
import app_links
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
|
@ -8,6 +9,15 @@ import Flutter
|
|||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
|
||||
if let url = AppLinks.shared.getLink(launchOptions: launchOptions) {
|
||||
AppLinks.shared.handleLink(url: url)
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
// return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,5 +77,9 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -20,7 +20,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
|||
|
||||
class App extends StatefulWidget {
|
||||
final Locale locale;
|
||||
const App({Key? key, this.locale = const Locale("en")}) : super(key: key);
|
||||
const App({super.key, this.locale = const Locale("en")});
|
||||
|
||||
static void setLocale(BuildContext context, Locale newLocale) {
|
||||
_AppState state = context.findAncestorStateOfType<_AppState>()!;
|
||||
|
|
|
@ -12,12 +12,12 @@ import 'package:ente_auth/models/key_attributes.dart';
|
|||
import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
class Configuration {
|
||||
|
@ -70,9 +70,10 @@ class Configuration {
|
|||
|
||||
Future<void> init() async {
|
||||
_preferences = await SharedPreferences.getInstance();
|
||||
sqfliteFfiInit();
|
||||
_secureStorage = const FlutterSecureStorage();
|
||||
_documentsDirectory = (await getApplicationDocumentsDirectory()).path;
|
||||
_tempDirectory = _documentsDirectory + "/temp/";
|
||||
_tempDirectory = "$_documentsDirectory/temp/";
|
||||
final tempDirectory = io.Directory(_tempDirectory);
|
||||
try {
|
||||
final currentTime = DateTime.now().microsecondsSinceEpoch;
|
||||
|
@ -160,7 +161,7 @@ class Configuration {
|
|||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
@ -170,28 +171,28 @@ class Configuration {
|
|||
CryptoUtil.encryptSync(masterKey, derivedKeyResult.key);
|
||||
|
||||
// Generate a public-private keypair and encrypt the latter
|
||||
final keyPair = await CryptoUtil.generateKeyPair();
|
||||
final keyPair = CryptoUtil.generateKeyPair();
|
||||
final encryptedSecretKeyData =
|
||||
CryptoUtil.encryptSync(keyPair.sk, masterKey);
|
||||
CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey);
|
||||
|
||||
final attributes = KeyAttributes(
|
||||
Sodium.bin2base64(kekSalt),
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
Sodium.bin2base64(keyPair.pk),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(kekSalt),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(keyPair.publicKey),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!),
|
||||
derivedKeyResult.memLimit,
|
||||
derivedKeyResult.opsLimit,
|
||||
Sodium.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedMasterKey.nonce!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
Sodium.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedMasterKey.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!),
|
||||
);
|
||||
final privateAttributes = PrivateKeyAttributes(
|
||||
Sodium.bin2base64(masterKey),
|
||||
Sodium.bin2hex(recoveryKey),
|
||||
Sodium.bin2base64(keyPair.sk),
|
||||
CryptoUtil.bin2base64(masterKey),
|
||||
CryptoUtil.bin2hex(recoveryKey),
|
||||
CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()),
|
||||
);
|
||||
return KeyGenResult(attributes, privateAttributes, loginKey);
|
||||
}
|
||||
|
@ -206,7 +207,7 @@ class Configuration {
|
|||
// decrypt the master key
|
||||
final kekSalt = CryptoUtil.getSaltToDeriveKey();
|
||||
final derivedKeyResult = await CryptoUtil.deriveSensitiveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
utf8.encode(password),
|
||||
kekSalt,
|
||||
);
|
||||
final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key);
|
||||
|
@ -218,9 +219,9 @@ class Configuration {
|
|||
final existingAttributes = getKeyAttributes();
|
||||
|
||||
final updatedAttributes = existingAttributes!.copyWith(
|
||||
kekSalt: Sodium.bin2base64(kekSalt),
|
||||
encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
kekSalt: CryptoUtil.bin2base64(kekSalt),
|
||||
encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
memLimit: derivedKeyResult.memLimit,
|
||||
opsLimit: derivedKeyResult.opsLimit,
|
||||
);
|
||||
|
@ -238,8 +239,8 @@ class Configuration {
|
|||
}) async {
|
||||
_logger.info('Start decryptAndSaveSecrets');
|
||||
keyEncryptionKey ??= await CryptoUtil.deriveKey(
|
||||
utf8.encode(password) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(password),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
|
@ -248,31 +249,31 @@ class Configuration {
|
|||
Uint8List key;
|
||||
try {
|
||||
key = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe('master-key failed, incorrect password?', e);
|
||||
throw Exception("Incorrect password");
|
||||
}
|
||||
_logger.info("master-key done");
|
||||
await setKey(Sodium.bin2base64(key));
|
||||
await setKey(CryptoUtil.bin2base64(key));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
key,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
_logger.info("secret-key done");
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
_logger.info('appToken done');
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
return keyEncryptionKey;
|
||||
}
|
||||
|
@ -291,28 +292,28 @@ class Configuration {
|
|||
Uint8List masterKey;
|
||||
try {
|
||||
masterKey = await CryptoUtil.decrypt(
|
||||
Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
Sodium.hex2bin(recoveryKey),
|
||||
Sodium.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey),
|
||||
CryptoUtil.hex2bin(recoveryKey),
|
||||
CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce),
|
||||
);
|
||||
} catch (e) {
|
||||
_logger.severe(e);
|
||||
rethrow;
|
||||
}
|
||||
await setKey(Sodium.bin2base64(masterKey));
|
||||
await setKey(CryptoUtil.bin2base64(masterKey));
|
||||
final secretKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedSecretKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedSecretKey),
|
||||
masterKey,
|
||||
Sodium.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce),
|
||||
);
|
||||
await setSecretKey(Sodium.bin2base64(secretKey));
|
||||
await setSecretKey(CryptoUtil.bin2base64(secretKey));
|
||||
final token = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(getEncryptedToken()!),
|
||||
Sodium.base642bin(attributes.publicKey),
|
||||
CryptoUtil.base642bin(getEncryptedToken()!),
|
||||
CryptoUtil.base642bin(attributes.publicKey),
|
||||
secretKey,
|
||||
);
|
||||
await setToken(
|
||||
Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe),
|
||||
CryptoUtil.bin2base64(token, urlSafe: true),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -400,27 +401,31 @@ class Configuration {
|
|||
}
|
||||
|
||||
Uint8List? getKey() {
|
||||
return _key == null ? null : Sodium.base642bin(_key!);
|
||||
return _key == null ? null : CryptoUtil.base642bin(_key!);
|
||||
}
|
||||
|
||||
Uint8List? getSecretKey() {
|
||||
return _secretKey == null ? null : Sodium.base642bin(_secretKey!);
|
||||
return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getAuthSecretKey() {
|
||||
return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!);
|
||||
return _authSecretKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_authSecretKey!);
|
||||
}
|
||||
|
||||
Uint8List? getOfflineSecretKey() {
|
||||
return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!);
|
||||
return _offlineAuthKey == null
|
||||
? null
|
||||
: CryptoUtil.base642bin(_offlineAuthKey!);
|
||||
}
|
||||
|
||||
Uint8List getRecoveryKey() {
|
||||
final keyAttributes = getKeyAttributes()!;
|
||||
return CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
|
||||
getKey()!,
|
||||
Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -447,7 +452,7 @@ class Configuration {
|
|||
iOptions: _secureStorageOptionsIOS,
|
||||
);
|
||||
} else {
|
||||
_offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey());
|
||||
_offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey());
|
||||
await _secureStorage.write(
|
||||
key: offlineAuthSecretKey,
|
||||
value: _offlineAuthKey,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<StreamedRequest> _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,
|
||||
|
|
|
@ -3,9 +3,10 @@ import 'dart:io';
|
|||
import 'package:dio/dio.dart';
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/core/constants.dart';
|
||||
import 'package:ente_auth/utils/package_info_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:fk_user_agent/fk_user_agent.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
|
@ -21,16 +22,22 @@ class Network {
|
|||
late Dio _enteDio;
|
||||
|
||||
Future<void> init() async {
|
||||
await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
if (PlatformUtil.isMobile()) await FkUserAgent.init();
|
||||
final packageInfo = await PackageInfoUtil().getPackageInfo();
|
||||
final version = PackageInfoUtil().getVersion(packageInfo);
|
||||
final packageName = PackageInfoUtil().getPackageName(packageInfo);
|
||||
|
||||
final preferences = await SharedPreferences.getInstance();
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
HttpHeaders.userAgentHeader: PlatformUtil.isMobile()
|
||||
? FkUserAgent.userAgent
|
||||
: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -38,11 +45,14 @@ class Network {
|
|||
_enteDio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiEndpoint,
|
||||
connectTimeout: kConnectTimeout,
|
||||
connectTimeout: Duration(milliseconds: kConnectTimeout),
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent,
|
||||
'X-Client-Version': packageInfo.version,
|
||||
'X-Client-Package': packageInfo.packageName,
|
||||
if (PlatformUtil.isMobile())
|
||||
HttpHeaders.userAgentHeader: FkUserAgent.userAgent
|
||||
else
|
||||
HttpHeaders.userAgentHeader: Platform.operatingSystem,
|
||||
'X-Client-Version': version,
|
||||
'X-Client-Package': packageName,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -76,8 +86,8 @@ class EnteRequestInterceptor extends Interceptor {
|
|||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (kDebugMode) {
|
||||
assert(
|
||||
options.baseUrl == enteEndpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
options.baseUrl == enteEndpoint,
|
||||
"interceptor should only be used for API endpoint",
|
||||
);
|
||||
}
|
||||
// ignore: prefer_const_constructors
|
||||
|
|
|
@ -11,6 +11,7 @@ final lightThemeData = ThemeData(
|
|||
iconTheme: const IconThemeData(color: Colors.black),
|
||||
primaryIconTheme:
|
||||
const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0),
|
||||
buttonTheme: const ButtonThemeData(),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
bgDisabled: const Color.fromRGBO(158, 158, 158, 1),
|
||||
bgEnabled: const Color.fromRGBO(0, 0, 0, 1),
|
||||
|
@ -72,24 +73,42 @@ final lightThemeData = ThemeData(
|
|||
? const Color.fromRGBO(255, 255, 255, 1)
|
||||
: const Color.fromRGBO(0, 0, 0, 1);
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.light(
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.light(
|
||||
primary: Colors.black,
|
||||
secondary: Color.fromARGB(255, 163, 163, 163),
|
||||
).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)),
|
||||
|
@ -105,6 +124,7 @@ final darkThemeData = ThemeData(
|
|||
hintColor: const Color.fromRGBO(158, 158, 158, 1),
|
||||
buttonTheme: const ButtonThemeData().copyWith(
|
||||
buttonColor: const Color.fromRGBO(45, 194, 98, 1.0),
|
||||
height: 56,
|
||||
),
|
||||
textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)),
|
||||
outlinedButtonTheme: buildOutlinedButtonThemeData(
|
||||
|
@ -164,24 +184,43 @@ final darkThemeData = ThemeData(
|
|||
return const Color.fromRGBO(158, 158, 158, 1);
|
||||
}
|
||||
}),
|
||||
), radioTheme: RadioThemeData(
|
||||
fillColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), switchTheme: SwitchThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
trackColor: MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) { return null; }
|
||||
if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); }
|
||||
return null;
|
||||
}),
|
||||
), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
),
|
||||
radioTheme: RadioThemeData(
|
||||
fillColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
switchTheme: SwitchThemeData(
|
||||
thumbColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
trackColor:
|
||||
MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return null;
|
||||
}
|
||||
if (states.contains(MaterialState.selected)) {
|
||||
return const Color.fromRGBO(102, 187, 106, 1);
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
),
|
||||
colorScheme: const ColorScheme.dark(primary: Colors.white)
|
||||
.copyWith(background: const Color.fromRGBO(0, 0, 0, 1)),
|
||||
);
|
||||
|
||||
TextTheme _buildTextTheme(Color textColor) {
|
||||
|
@ -400,6 +439,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({
|
|||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
fixedSize: const Size.fromHeight(56),
|
||||
alignment: Alignment.center,
|
||||
padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
|
||||
textStyle: const TextStyle(
|
||||
|
@ -436,7 +476,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({
|
|||
}) {
|
||||
return ElevatedButtonThemeData(
|
||||
style: ElevatedButton.styleFrom(
|
||||
foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation,
|
||||
foregroundColor: onPrimary,
|
||||
backgroundColor: primary,
|
||||
elevation: elevation,
|
||||
alignment: Alignment.center,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
|
|
|
@ -10,12 +10,12 @@ class AuthenticatorGateway {
|
|||
late String _basedEndpoint;
|
||||
|
||||
AuthenticatorGateway(this._dio, this._config) {
|
||||
_basedEndpoint = _config.getHttpEndpoint() + "/authenticator";
|
||||
_basedEndpoint = "${_config.getHttpEndpoint()}/authenticator";
|
||||
}
|
||||
|
||||
Future<void> createKey(String encKey, String header) async {
|
||||
await _dio.post(
|
||||
_basedEndpoint + "/key",
|
||||
"$_basedEndpoint/key",
|
||||
data: {
|
||||
"encryptedKey": encKey,
|
||||
"header": header,
|
||||
|
@ -31,7 +31,7 @@ class AuthenticatorGateway {
|
|||
Future<AuthKey> getKey() async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/key",
|
||||
"$_basedEndpoint/key",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -39,7 +39,7 @@ class AuthenticatorGateway {
|
|||
),
|
||||
);
|
||||
return AuthKey.fromMap(response.data);
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null && (e.response!.statusCode ?? 0) == 404) {
|
||||
throw AuthenticatorKeyNotFound();
|
||||
} else {
|
||||
|
@ -52,7 +52,7 @@ class AuthenticatorGateway {
|
|||
|
||||
Future<AuthEntity> createEntity(String encryptedData, String header) async {
|
||||
final response = await _dio.post(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
data: {
|
||||
"encryptedData": encryptedData,
|
||||
"header": header,
|
||||
|
@ -72,7 +72,7 @@ class AuthenticatorGateway {
|
|||
String header,
|
||||
) async {
|
||||
await _dio.put(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
data: {
|
||||
"id": id,
|
||||
"encryptedData": encryptedData,
|
||||
|
@ -90,7 +90,7 @@ class AuthenticatorGateway {
|
|||
String id,
|
||||
) async {
|
||||
await _dio.delete(
|
||||
_basedEndpoint + "/entity",
|
||||
"$_basedEndpoint/entity",
|
||||
queryParameters: {
|
||||
"id": id,
|
||||
},
|
||||
|
@ -105,7 +105,7 @@ class AuthenticatorGateway {
|
|||
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
_basedEndpoint + "/entity/diff",
|
||||
"$_basedEndpoint/entity/diff",
|
||||
queryParameters: {
|
||||
"sinceTime": sinceTime,
|
||||
"limit": limit,
|
||||
|
@ -124,7 +124,7 @@ class AuthenticatorGateway {
|
|||
}
|
||||
return authEntities;
|
||||
} catch (e) {
|
||||
if (e is DioError && e.response?.statusCode == 401) {
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
throw UnauthorizedError();
|
||||
} else {
|
||||
rethrow;
|
||||
|
|
|
@ -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!"
|
||||
}
|
|
@ -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<void> _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<void> _init(bool bool, {String? via}) async {
|
||||
// Start workers asynchronously. No need to wait for them to start
|
||||
Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode);
|
||||
CryptoUtil.init();
|
||||
_registerWindowsProtocol();
|
||||
await initCryptoUtil();
|
||||
|
||||
await PreferenceService.instance.init();
|
||||
await CodeStore.instance.init();
|
||||
await Configuration.instance.init();
|
||||
|
@ -95,6 +124,7 @@ Future<void> _init(bool bool, {String? via}) async {
|
|||
}
|
||||
|
||||
Future<void> _setupPrivacyScreen() async {
|
||||
if (!PlatformUtil.isMobile()) return;
|
||||
final brightness =
|
||||
SchedulerBinding.instance.platformDispatcher.platformBrightness;
|
||||
bool isInDarkMode = brightness == Brightness.dark;
|
||||
|
|
|
@ -57,14 +57,7 @@ class Code {
|
|||
updatedAlgo,
|
||||
updatedType,
|
||||
updatedCounter,
|
||||
"otpauth://${updatedType.name}/" +
|
||||
updateIssuer +
|
||||
":" +
|
||||
updateAccount +
|
||||
"?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" +
|
||||
updateIssuer +
|
||||
"&period=$updatePeriod&secret=" +
|
||||
updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""),
|
||||
"otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}",
|
||||
generatedID: generatedID,
|
||||
);
|
||||
}
|
||||
|
@ -83,14 +76,7 @@ class Code {
|
|||
Algorithm.sha1,
|
||||
Type.totp,
|
||||
0,
|
||||
"otpauth://totp/" +
|
||||
issuer +
|
||||
":" +
|
||||
account +
|
||||
"?algorithm=SHA1&digits=6&issuer=" +
|
||||
issuer +
|
||||
"&period=30&secret=" +
|
||||
secret,
|
||||
"otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret",
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class DerivedKeyResult {
|
||||
final Uint8List key;
|
||||
final int memLimit;
|
||||
final int opsLimit;
|
||||
|
||||
DerivedKeyResult(this.key, this.memLimit, this.opsLimit);
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
class EncryptionResult {
|
||||
final Uint8List? encryptedData;
|
||||
final Uint8List? key;
|
||||
final Uint8List? header;
|
||||
final Uint8List? nonce;
|
||||
|
||||
EncryptionResult({
|
||||
this.encryptedData,
|
||||
this.key,
|
||||
this.header,
|
||||
this.nonce,
|
||||
});
|
||||
}
|
|
@ -7,26 +7,18 @@ import 'package:ente_auth/ente_theme_data.dart';
|
|||
import 'package:ente_auth/events/trigger_logout_event.dart';
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import 'package:ente_auth/locale.dart';
|
||||
import 'package:ente_auth/theme/text_style.dart';
|
||||
import 'package:ente_auth/ui/account/email_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/login_page.dart';
|
||||
import 'package:ente_auth/ui/account/logout_dialog.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/account/password_reentry_page.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/components/models/button_result.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/ui/settings/language_picker.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
|
||||
class OnboardingPage extends StatefulWidget {
|
||||
const OnboardingPage({Key? key}) : super(key: key);
|
||||
const OnboardingPage({super.key});
|
||||
|
||||
@override
|
||||
State<OnboardingPage> createState() => _OnboardingPageState();
|
||||
|
@ -56,114 +48,128 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
|||
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<OnboardingPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _optForOfflineMode() async {
|
||||
bool canCheckBio = await LocalAuthentication().canCheckBiometrics;
|
||||
if (!canCheckBio) {
|
||||
showToast(
|
||||
context,
|
||||
"Sorry, biometric authentication is not supported on this device.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode();
|
||||
ButtonResult? result;
|
||||
if (!hasOptedBefore) {
|
||||
result = await showChoiceActionSheet(
|
||||
context,
|
||||
title: context.l10n.warning,
|
||||
body: context.l10n.offlineModeWarning,
|
||||
secondButtonLabel: context.l10n.cancel,
|
||||
firstButtonLabel: context.l10n.ok,
|
||||
);
|
||||
}
|
||||
if (hasOptedBefore || result?.action == ButtonAction.first) {
|
||||
await Configuration.instance.optForOfflineMode();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const HomePage();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: Remove After Stable
|
||||
// Future<void> _optForOfflineMode() async {
|
||||
// final canContinue = Platform.isMacOS || Platform.isLinux
|
||||
// ? true
|
||||
// : await LocalAuthentication().canCheckBiometrics;
|
||||
|
||||
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;
|
||||
|
|
|
@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
|||
class SetupEnterSecretKeyPage extends StatefulWidget {
|
||||
final Code? code;
|
||||
|
||||
SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key);
|
||||
SetupEnterSecretKeyPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
State<SetupEnterSecretKeyPage> createState() =>
|
||||
|
@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
|||
widget.code != null ? safeDecode(widget.code!.account).trim() : null,
|
||||
);
|
||||
_secretController = TextEditingController(
|
||||
text: widget.code != null ? widget.code!.secret : null,
|
||||
text: widget.code?.secret,
|
||||
);
|
||||
_secretKeyObscured = widget.code != null;
|
||||
super.initState();
|
||||
|
@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
|||
appBar: AppBar(
|
||||
title: Text(l10n.importAccountPageTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
|
@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart';
|
|||
class ViewQrPage extends StatelessWidget {
|
||||
final Code? code;
|
||||
|
||||
ViewQrPage({this.code, Key? key}) : super(key: key);
|
||||
ViewQrPage({this.code, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget {
|
|||
appBar: AppBar(
|
||||
title: Text(l10n.qrCode),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
QrImage(
|
||||
QrImageView(
|
||||
data: code!.rawData,
|
||||
foregroundColor: Theme.of(context).colorScheme.onBackground,
|
||||
eyeStyle: QrEyeStyle(
|
||||
eyeShape: QrEyeShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
dataModuleShape: QrDataModuleShape.square,
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
version: QrVersions.auto,
|
||||
size: qrSize,
|
||||
),
|
||||
|
|
|
@ -16,9 +16,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart';
|
|||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -26,6 +25,7 @@ enum AccountMode {
|
|||
online,
|
||||
offline,
|
||||
}
|
||||
|
||||
extension on AccountMode {
|
||||
bool get isOnline => this == AccountMode.online;
|
||||
bool get isOffline => this == AccountMode.offline;
|
||||
|
@ -75,10 +75,10 @@ class AuthenticatorService {
|
|||
final key = await getOrCreateAuthDataKey(mode);
|
||||
for (LocalAuthEntity e in result) {
|
||||
try {
|
||||
final decryptedValue = await CryptoUtil.decryptChaCha(
|
||||
Sodium.base642bin(e.encryptedData),
|
||||
final decryptedValue = await CryptoUtil.decryptData(
|
||||
CryptoUtil.base642bin(e.encryptedData),
|
||||
key,
|
||||
Sodium.base642bin(e.header),
|
||||
CryptoUtil.base642bin(e.header),
|
||||
);
|
||||
final hasSynced = !(e.id == null || e.shouldSync);
|
||||
entities.add(
|
||||
|
@ -101,12 +101,13 @@ class AuthenticatorService {
|
|||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final insertedID = accountMode.isOnline
|
||||
? await _db.insert(encryptedData, header)
|
||||
: await _offlineDb.insert(encryptedData, header);
|
||||
|
@ -123,12 +124,13 @@ class AuthenticatorService {
|
|||
AccountMode accountMode,
|
||||
) async {
|
||||
var key = await getOrCreateAuthDataKey(accountMode);
|
||||
final encryptedKeyData = await CryptoUtil.encryptChaCha(
|
||||
utf8.encode(plainText) as Uint8List,
|
||||
final encryptedKeyData = await CryptoUtil.encryptData(
|
||||
utf8.encode(plainText),
|
||||
key,
|
||||
);
|
||||
String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = Sodium.bin2base64(encryptedKeyData.header!);
|
||||
String encryptedData =
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!);
|
||||
String header = CryptoUtil.bin2base64(encryptedKeyData.header!);
|
||||
final int affectedRows = accountMode.isOnline
|
||||
? await _db.updateEntry(generatedID, encryptedData, header)
|
||||
: await _offlineDb.updateEntry(generatedID, encryptedData, header);
|
||||
|
@ -154,7 +156,7 @@ class AuthenticatorService {
|
|||
} else {
|
||||
debugPrint("Skipping delete since account mode is offline");
|
||||
}
|
||||
if(accountMode.isOnline) {
|
||||
if (accountMode.isOnline) {
|
||||
await _db.deleteByIDs(generatedIDs: [genID]);
|
||||
} else {
|
||||
await _offlineDb.deleteByIDs(generatedIDs: [genID]);
|
||||
|
@ -163,7 +165,7 @@ class AuthenticatorService {
|
|||
|
||||
Future<bool> onlineSync() async {
|
||||
try {
|
||||
if(getAccountMode().isOffline) {
|
||||
if (getAccountMode().isOffline) {
|
||||
debugPrint("Skipping sync since account mode is offline");
|
||||
return false;
|
||||
}
|
||||
|
@ -191,25 +193,25 @@ class AuthenticatorService {
|
|||
Future<void> _remoteToLocalSync() async {
|
||||
_logger.info('Initiating remote to local sync');
|
||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||
_logger.info("Current sync is " + lastSyncTime.toString());
|
||||
_logger.info("Current sync is $lastSyncTime");
|
||||
const int fetchLimit = 500;
|
||||
final List<AuthEntity> result =
|
||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
||||
_logger.info(result.length.toString() + " entries fetched from remote");
|
||||
_logger.info("${result.length} entries fetched from remote");
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final maxSyncTime = result.map((e) => e.updatedAt).reduce(max);
|
||||
List<String> deletedIDs =
|
||||
result.where((element) => element.isDeleted).map((e) => e.id).toList();
|
||||
_logger.info(deletedIDs.length.toString() + " entries deleted");
|
||||
_logger.info("${deletedIDs.length} entries deleted");
|
||||
result.removeWhere((element) => element.isDeleted);
|
||||
await _db.insertOrReplace(result);
|
||||
if (deletedIDs.isNotEmpty) {
|
||||
await _db.deleteByIDs(ids: deletedIDs);
|
||||
}
|
||||
_prefs.setInt(_lastEntitySyncTime, maxSyncTime);
|
||||
_logger.info("Setting synctime to " + maxSyncTime.toString());
|
||||
_logger.info("Setting synctime to $maxSyncTime");
|
||||
if (result.length == fetchLimit) {
|
||||
_logger.info("Diff limit reached, pulling again");
|
||||
await _remoteToLocalSync();
|
||||
|
@ -223,7 +225,7 @@ class AuthenticatorService {
|
|||
.where((element) => element.shouldSync || element.id == null)
|
||||
.toList();
|
||||
_logger.info(
|
||||
pendingUpdate.length.toString() + " entries to be updated at remote",
|
||||
"${pendingUpdate.length} entries to be updated at remote",
|
||||
);
|
||||
for (LocalAuthEntity entity in pendingUpdate) {
|
||||
if (entity.id == null) {
|
||||
|
@ -253,7 +255,7 @@ class AuthenticatorService {
|
|||
}
|
||||
|
||||
Future<Uint8List> getOrCreateAuthDataKey(AccountMode mode) async {
|
||||
if(mode.isOffline) {
|
||||
if (mode.isOffline) {
|
||||
return _config.getOfflineSecretKey()!;
|
||||
}
|
||||
if (_config.getAuthSecretKey() != null) {
|
||||
|
@ -262,21 +264,21 @@ class AuthenticatorService {
|
|||
try {
|
||||
final AuthKey response = await _gateway.getKey();
|
||||
final authKey = CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(response.encryptedKey),
|
||||
CryptoUtil.base642bin(response.encryptedKey),
|
||||
_config.getKey()!,
|
||||
Sodium.base642bin(response.header),
|
||||
CryptoUtil.base642bin(response.header),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(authKey));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey));
|
||||
return authKey;
|
||||
} on AuthenticatorKeyNotFound catch (e) {
|
||||
_logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}");
|
||||
final key = CryptoUtil.generateKey();
|
||||
final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
|
||||
await _gateway.createKey(
|
||||
Sodium.bin2base64(encryptedKeyData.encryptedData!),
|
||||
Sodium.bin2base64(encryptedKeyData.nonce!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptedKeyData.nonce!),
|
||||
);
|
||||
await _config.setAuthSecretKey(Sodium.bin2base64(key));
|
||||
await _config.setAuthSecretKey(CryptoUtil.bin2base64(key));
|
||||
return key;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Failed to getOrCreateAuthDataKey", e, s);
|
||||
|
|
|
@ -28,6 +28,8 @@ class BillingService {
|
|||
final _dio = Network.instance.getDio();
|
||||
final _config = Configuration.instance;
|
||||
|
||||
bool _isOnSubscriptionPage = false;
|
||||
|
||||
Subscription? _cachedSubscription;
|
||||
|
||||
Future<BillingPlans>? _future;
|
||||
|
@ -50,7 +52,7 @@ class BillingService {
|
|||
|
||||
Future<Response<dynamic>> _fetchPrivateBillingPlans() {
|
||||
return _dio.get(
|
||||
_config.getHttpEndpoint() + "/billing/user-plans/",
|
||||
"${_config.getHttpEndpoint()}/billing/user-plans/",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -60,7 +62,7 @@ class BillingService {
|
|||
}
|
||||
|
||||
Future<Response<dynamic>> _fetchPublicBillingPlans() {
|
||||
return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2");
|
||||
return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2");
|
||||
}
|
||||
|
||||
Future<Subscription> verifySubscription(
|
||||
|
@ -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<Subscription> cancelStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/cancel-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -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<Subscription> activateStripeSubscription() async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/billing/stripe/activate-subscription",
|
||||
"${_config.getHttpEndpoint()}/billing/stripe/activate-subscription",
|
||||
options: Options(
|
||||
headers: {
|
||||
"X-Auth-Token": _config.getToken(),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
import 'package:ente_auth/utils/auth_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_authentication/flutter_local_authentication.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class LocalAuthenticationService {
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
static final LocalAuthenticationService instance =
|
||||
LocalAuthenticationService._privateConstructor();
|
||||
final logger = Logger((LocalAuthenticationService).toString());
|
||||
|
||||
Future<bool> requestLocalAuthentication(
|
||||
BuildContext context,
|
||||
|
@ -38,7 +44,7 @@ class LocalAuthenticationService {
|
|||
String errorDialogContent, [
|
||||
String errorDialogTitle = "",
|
||||
]) async {
|
||||
if (await LocalAuthentication().isDeviceSupported()) {
|
||||
if (await _isLocalAuthSupportedOnDevice()) {
|
||||
AppLock.of(context)!.disable();
|
||||
final result = await requestAuthentication(
|
||||
context,
|
||||
|
@ -64,6 +70,12 @@ class LocalAuthenticationService {
|
|||
}
|
||||
|
||||
Future<bool> _isLocalAuthSupportedOnDevice() async {
|
||||
return await LocalAuthentication().isDeviceSupported();
|
||||
try {
|
||||
return Platform.isMacOS || Platform.isLinux
|
||||
? await FlutterLocalAuthentication().canAuthenticate()
|
||||
: await LocalAuthentication().isDeviceSupported();
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class NotificationService {
|
|||
_flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
if (implementation != null) {
|
||||
implementation.requestPermission();
|
||||
implementation.requestNotificationsPermission();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class UserRemoteFlagService {
|
|||
queryParams["defaultValue"] = defaultValue;
|
||||
}
|
||||
final response = await _dio.get(
|
||||
_config.getHttpEndpoint() + "/remote-store",
|
||||
"${_config.getHttpEndpoint()}/remote-store",
|
||||
queryParameters: queryParams,
|
||||
options: Options(
|
||||
headers: {
|
||||
|
@ -119,7 +119,7 @@ class UserRemoteFlagService {
|
|||
Future<void> _updateKeyValue(String key, String value) async {
|
||||
try {
|
||||
final response = await _dio.post(
|
||||
_config.getHttpEndpoint() + "/remote-store/update",
|
||||
"${_config.getHttpEndpoint()}/remote-store/update",
|
||||
data: {
|
||||
"key": key,
|
||||
"value": value,
|
||||
|
|
|
@ -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<void> leaveFamilyPlan() async {
|
||||
try {
|
||||
await _enteDio.delete("/family/leave");
|
||||
} on DioError catch (e) {
|
||||
} on DioException catch (e) {
|
||||
_logger.warning('failed to leave family plan', e);
|
||||
rethrow;
|
||||
}
|
||||
|
@ -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<SrpAttributes> 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");
|
||||
|
|
|
@ -3,10 +3,12 @@ import 'dart:io';
|
|||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class AuthenticatorDB {
|
||||
static const _databaseName = "ente.authenticator.db";
|
||||
|
@ -25,6 +27,16 @@ class AuthenticatorDB {
|
|||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
|
@ -166,7 +178,7 @@ class AuthenticatorDB {
|
|||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@ import 'dart:io';
|
|||
|
||||
import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite/sqflite.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
|
||||
class OfflineAuthenticatorDB {
|
||||
static const _databaseName = "ente.offline_authenticator.db";
|
||||
|
@ -15,7 +16,8 @@ class OfflineAuthenticatorDB {
|
|||
static const entityTable = 'entities';
|
||||
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor();
|
||||
static final OfflineAuthenticatorDB instance =
|
||||
OfflineAuthenticatorDB._privateConstructor();
|
||||
|
||||
static Future<Database>? _dbFuture;
|
||||
|
||||
|
@ -25,8 +27,18 @@ class OfflineAuthenticatorDB {
|
|||
}
|
||||
|
||||
Future<Database> _initDatabase() async {
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
var databaseFactory = databaseFactoryFfi;
|
||||
return await databaseFactory.openDatabase(
|
||||
await DirectoryUtils.getDatabasePath(_databaseName),
|
||||
options: OpenDatabaseOptions(
|
||||
version: _databaseVersion,
|
||||
onCreate: _onCreate,
|
||||
),
|
||||
);
|
||||
}
|
||||
final Directory documentsDirectory =
|
||||
await getApplicationDocumentsDirectory();
|
||||
await getApplicationDocumentsDirectory();
|
||||
final String path = join(documentsDirectory.path, _databaseName);
|
||||
debugPrint(path);
|
||||
return await openDatabase(
|
||||
|
@ -70,10 +82,10 @@ class OfflineAuthenticatorDB {
|
|||
}
|
||||
|
||||
Future<int> updateEntry(
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
int generatedID,
|
||||
String encData,
|
||||
String header,
|
||||
) async {
|
||||
final db = await instance.database;
|
||||
final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch;
|
||||
int affectedRows = await db.update(
|
||||
|
@ -151,7 +163,7 @@ class OfflineAuthenticatorDB {
|
|||
batch.delete(entityTable, where: whereID, whereArgs: [id]);
|
||||
}
|
||||
}
|
||||
await batch.commit();
|
||||
final _ = await batch.commit();
|
||||
debugPrint("Done");
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1);
|
|||
const Color _warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
const Color _warning800 = Color(0xFFF53434);
|
||||
const Color warning500 = Color.fromRGBO(255, 101, 101, 1);
|
||||
// ignore: unused_element
|
||||
const Color _warning400 = Color.fromRGBO(255, 111, 111, 1);
|
||||
|
||||
const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChangeEmailDialog extends StatefulWidget {
|
||||
const ChangeEmailDialog({Key? key}) : super(key: key);
|
||||
const ChangeEmailDialog({super.key});
|
||||
|
||||
@override
|
||||
State<ChangeEmailDialog> createState() => _ChangeEmailDialogState();
|
||||
|
|
|
@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/common/dialogs.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
|
||||
class DeleteAccountPage extends StatelessWidget {
|
||||
const DeleteAccountPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget {
|
|||
l10n.initiateAccountDeleteTitle,
|
||||
);
|
||||
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
final choice = await showChoiceDialogOld(
|
||||
context,
|
||||
|
@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget {
|
|||
return;
|
||||
}
|
||||
final decryptChallenge = CryptoUtil.openSealSync(
|
||||
Sodium.base642bin(response.encryptedChallenge),
|
||||
Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey),
|
||||
CryptoUtil.base642bin(response.encryptedChallenge),
|
||||
CryptoUtil.base642bin(
|
||||
Configuration.instance.getKeyAttributes()!.publicKey,
|
||||
),
|
||||
Configuration.instance.getSecretKey()!,
|
||||
);
|
||||
final challengeResponseStr = utf8.decode(decryptChallenge);
|
||||
|
|
|
@ -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<EmailEntryPage> createState() => _EmailEntryPageState();
|
||||
|
@ -427,15 +427,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -443,15 +438,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -494,15 +484,10 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
|
|||
tags: {
|
||||
'underline': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.encryption,
|
||||
"https://ente.io/architecture",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
|
|
@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart';
|
|||
import 'package:ente_auth/services/user_service.dart';
|
||||
import 'package:ente_auth/ui/account/login_pwd_verification_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/common/web_page.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import "package:styled_text/styled_text.dart";
|
||||
|
||||
class LoginPage extends StatefulWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
const LoginPage({super.key});
|
||||
|
||||
@override
|
||||
State<LoginPage> createState() => _LoginPageState();
|
||||
|
@ -25,6 +25,36 @@ class _LoginPageState extends State<LoginPage> {
|
|||
Color? _emailInputFieldColor;
|
||||
final Logger _logger = Logger('_LoginPageState');
|
||||
|
||||
Future<void> onPressed() async {
|
||||
UserService.instance.setEmail(_email!);
|
||||
Configuration.instance.resetVolatilePassword();
|
||||
SrpAttributes? attr;
|
||||
bool isEmailVerificationEnabled = true;
|
||||
try {
|
||||
attr = await UserService.instance.getSrpAttributes(_email!);
|
||||
isEmailVerificationEnabled = attr.isEmailMFAEnabled;
|
||||
} catch (e) {
|
||||
if (e is! SrpSetupNotCompleteError) {
|
||||
_logger.severe('Error getting SRP attributes', e);
|
||||
}
|
||||
}
|
||||
if (attr != null && !isEmailVerificationEnabled) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return LoginPasswordVerificationPage(
|
||||
srpAttributes: attr!,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await UserService.instance
|
||||
.sendOtt(context, _email!, isCreateAccountScreen: false);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_email = _config.getEmail();
|
||||
|
@ -60,35 +90,7 @@ class _LoginPageState extends State<LoginPage> {
|
|||
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<LoginPage> {
|
|||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: TextFormField(
|
||||
autofillHints: const [AutofillHints.email],
|
||||
onFieldSubmitted:
|
||||
_emailIsValid ? (value) => onPressed() : null,
|
||||
decoration: InputDecoration(
|
||||
fillColor: _emailInputFieldColor,
|
||||
filled: true,
|
||||
|
@ -178,15 +182,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
tags: {
|
||||
'u-terms': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.termsOfServicesTitle,
|
||||
"https://ente.io/terms",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
@ -194,15 +193,10 @@ class _LoginPageState extends State<LoginPage> {
|
|||
),
|
||||
'u-policy': StyledTextActionTag(
|
||||
(String? text, Map<String?, String?> attrs) =>
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.privacyPolicyTitle,
|
||||
"https://ente.io/privacy",
|
||||
),
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
|
|
|
@ -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<LoginPasswordVerificationPage> createState() =>
|
||||
|
@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState
|
|||
bool _passwordInFocus = false;
|
||||
bool _passwordVisible = false;
|
||||
|
||||
Future<void> 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(
|
||||
|
|
|
@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget {
|
|||
this.isChangeEmail = false,
|
||||
this.isCreateAccountScreen = false,
|
||||
this.isResetPasswordScreen = false,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<OTTVerificationPage> createState() => _OTTVerificationPageState();
|
||||
|
@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget {
|
|||
class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
||||
final _verificationCodeController = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
if (widget.isChangeEmail) {
|
||||
await UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
await UserService.instance.verifyEmail(
|
||||
context,
|
||||
_verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,
|
||||
);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
|||
body: _getBody(),
|
||||
floatingActionButton: DynamicFAB(
|
||||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: !(_verificationCodeController.text.isEmpty),
|
||||
isFormValid: _verificationCodeController.text.isNotEmpty,
|
||||
buttonText: l10n.verify,
|
||||
onPressedFunction: () {
|
||||
if (widget.isChangeEmail) {
|
||||
UserService.instance.changeEmail(
|
||||
context,
|
||||
widget.email,
|
||||
_verificationCodeController.text,
|
||||
);
|
||||
} else {
|
||||
UserService.instance
|
||||
.verifyEmail(context, _verificationCodeController.text,
|
||||
isResettingPasswordScreen: widget.isResetPasswordScreen,);
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
|
|||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
|
||||
child: TextFormField(
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
onFieldSubmitted: _verificationCodeController.text.isNotEmpty
|
||||
? (_) => onPressed()
|
||||
: null,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
hintText: l10n.tapToEnterCode,
|
||||
|
|
|
@ -4,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<PasswordEntryPage> createState() => _PasswordEntryPageState();
|
||||
|
@ -343,15 +343,10 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
|||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WebPage(
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
),
|
||||
PlatformUtil.openWebView(
|
||||
context,
|
||||
context.l10n.howItWorks,
|
||||
"https://ente.io/architecture",
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
|
|
|
@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart';
|
|||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/ui/home_page.dart';
|
||||
import 'package:ente_auth/utils/crypto_util.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/email_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class PasswordReentryPage extends StatefulWidget {
|
||||
const PasswordReentryPage({Key? key}) : super(key: key);
|
||||
const PasswordReentryPage({super.key});
|
||||
|
||||
@override
|
||||
State<PasswordReentryPage> createState() => _PasswordReentryPageState();
|
||||
|
@ -260,8 +260,8 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
|
|||
),
|
||||
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<PasswordReentryPage> {
|
|||
),
|
||||
);
|
||||
},
|
||||
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<PasswordReentryPage> {
|
|||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
|
@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart';
|
|||
import 'package:ente_auth/ente_theme_data.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/common/gradient_button.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/share_utils.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:file_saver/file_saver.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
const RecoveryKeyPage(
|
||||
this.recoveryKey,
|
||||
this.doneText, {
|
||||
Key? key,
|
||||
super.key,
|
||||
this.showAppBar,
|
||||
this.onDone,
|
||||
this.isDismissible,
|
||||
|
@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
this.text,
|
||||
this.subText,
|
||||
this.showProgressBar = false,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<RecoveryKeyPage> createState() => _RecoveryKeyPageState();
|
||||
|
@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget {
|
|||
class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
||||
bool _hasTriedToSave = false;
|
||||
final _recoveryKeyFile = io.File(
|
||||
Configuration.instance.getTempDirectory() + "ente-recovery-key.txt",
|
||||
"${Configuration.instance.getTempDirectory()}ente-recovery-key.txt",
|
||||
);
|
||||
|
||||
@override
|
||||
|
@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
? 32
|
||||
: 120;
|
||||
|
||||
Future<void> copy() async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: recoveryKey,
|
||||
),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: widget.showProgressBar
|
||||
? AppBar(
|
||||
|
@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Padding(padding: EdgeInsets.only(top: 24)),
|
||||
DottedBorder(
|
||||
color: const Color.fromARGB(255, 105, 17, 127),
|
||||
//color of dotted/dash line
|
||||
strokeWidth: 1,
|
||||
//thickness of dash/dots
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
//dash patterns, 10 is dash width, 6 is space width
|
||||
child: SizedBox(
|
||||
//inner container
|
||||
// height: 120, //height of inner container
|
||||
width: double
|
||||
.infinity, //width to 100% match to parent container.
|
||||
// ignore: prefer_const_literals_to_create_immutables
|
||||
child: Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: recoveryKey),
|
||||
);
|
||||
showShortToast(
|
||||
context,
|
||||
context.l10n.recoveryKeyCopiedToClipboard,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: const Color.fromRGBO(
|
||||
49,
|
||||
155,
|
||||
86,
|
||||
.2,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0x8E9610D6),
|
||||
Color(0x8E9F4FC6),
|
||||
],
|
||||
stops: [0.0, 0.9753],
|
||||
),
|
||||
),
|
||||
child: DottedBorder(
|
||||
padding: EdgeInsets.zero,
|
||||
borderType: BorderType.RRect,
|
||||
strokeWidth: 1,
|
||||
color: const Color(0xFF6B6B6B),
|
||||
dashPattern: const [6, 6],
|
||||
radius: const Radius.circular(8),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Stack(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
final content = Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
textAlign: TextAlign.justify,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge,
|
||||
),
|
||||
);
|
||||
|
||||
if (PlatformUtil.isMobile()) {
|
||||
return GestureDetector(
|
||||
onTap: () async => await copy(),
|
||||
child: content,
|
||||
);
|
||||
} else {
|
||||
return SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls:
|
||||
PlatformUtil.selectionControls,
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(2),
|
||||
),
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.recoveryKeyBoxColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
child: Text(
|
||||
recoveryKey,
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: PlatformCopy(
|
||||
onPressed: copy,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
),
|
||||
),
|
||||
],
|
||||
), // columnEnds
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
final List<Widget> childrens = [];
|
||||
if (!_hasTriedToSave) {
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
style: Theme.of(context).colorScheme.optionalActionButtonStyle,
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
child: Text(context.l10n.doThisLater),
|
||||
),
|
||||
),
|
||||
);
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
|
@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
childrens.add(
|
||||
GradientButton(
|
||||
onTap: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
shareDialog(
|
||||
context,
|
||||
context.l10n.recoveryKey,
|
||||
saveAction: () async {
|
||||
await _saveRecoveryKey(recoveryKey);
|
||||
},
|
||||
sendAction: () async {
|
||||
await _shareRecoveryKey(recoveryKey);
|
||||
},
|
||||
);
|
||||
},
|
||||
text: context.l10n.saveKey,
|
||||
),
|
||||
);
|
||||
|
||||
if (_hasTriedToSave) {
|
||||
childrens.add(const SizedBox(height: 10));
|
||||
childrens.add(
|
||||
ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
SizedBox(
|
||||
height: 56,
|
||||
child: ElevatedButton(
|
||||
child: Text(widget.doneText),
|
||||
onPressed: () async {
|
||||
await _saveKeys();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
return childrens;
|
||||
}
|
||||
|
||||
Future _saveRecoveryKey(String recoveryKey) async {
|
||||
final bytes = utf8.encode(recoveryKey);
|
||||
final time = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
await PlatformUtil.shareFile(
|
||||
"ente_recovery_key_$time",
|
||||
"txt",
|
||||
bytes,
|
||||
MimeType.text,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
showToast(
|
||||
context,
|
||||
context.l10n.recoveryKeySaved,
|
||||
);
|
||||
setState(() {
|
||||
_hasTriedToSave = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future _shareRecoveryKey(String recoveryKey) async {
|
||||
if (_recoveryKeyFile.existsSync()) {
|
||||
await _recoveryKeyFile.delete();
|
||||
}
|
||||
_recoveryKeyFile.writeAsStringSync(recoveryKey);
|
||||
// ignore: deprecated_member_use
|
||||
await Share.shareFiles([_recoveryKeyFile.path]);
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (mounted) {
|
||||
|
@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
|
|||
widget.onDone!();
|
||||
}
|
||||
}
|
||||
|
||||
class PlatformCopy extends StatelessWidget {
|
||||
const PlatformCopy({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final void Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () => onPressed(),
|
||||
visualDensity: VisualDensity.compact,
|
||||
icon: const Icon(
|
||||
Icons.copy,
|
||||
size: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/l10n/l10n.dart';
|
||||
import 'package:ente_auth/ui/account/password_entry_page.dart';
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
|
@ -10,7 +9,7 @@ import 'package:ente_auth/utils/toast_util.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class RecoveryPage extends StatefulWidget {
|
||||
const RecoveryPage({Key? key}) : super(key: key);
|
||||
const RecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<RecoveryPage> createState() => _RecoveryPageState();
|
||||
|
@ -19,6 +18,36 @@ class RecoveryPage extends StatefulWidget {
|
|||
class _RecoveryPageState extends State<RecoveryPage> {
|
||||
final _recoveryKey = TextEditingController();
|
||||
|
||||
Future<void> onPressed() async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const PopScope(
|
||||
canPop: false,
|
||||
child: PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
|
||||
|
@ -46,35 +75,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
isKeypadOpen: isKeypadOpen,
|
||||
isFormValid: _recoveryKey.text.isNotEmpty,
|
||||
buttonText: 'Recover',
|
||||
onPressedFunction: () async {
|
||||
FocusScope.of(context).unfocus();
|
||||
final dialog = createProgressDialog(context, "Decrypting...");
|
||||
await dialog.show();
|
||||
try {
|
||||
await Configuration.instance.recover(_recoveryKey.text.trim());
|
||||
await dialog.hide();
|
||||
showToast(context, "Recovery successful!");
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: const PasswordEntryPage(
|
||||
mode: PasswordEntryMode.reset,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
String errMessage = 'The recovery key you entered is incorrect';
|
||||
if (e is AssertionError) {
|
||||
errMessage = '$errMessage : ${e.message}';
|
||||
}
|
||||
showErrorDialog(context, "Incorrect recovery key", errMessage);
|
||||
}
|
||||
},
|
||||
onPressedFunction: onPressed,
|
||||
),
|
||||
floatingActionButtonLocation: fabLocation(),
|
||||
floatingActionButtonAnimator: NoScalingAnimation(),
|
||||
|
@ -87,7 +88,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 30, horizontal: 20),
|
||||
child: Text(
|
||||
'Forgot password',
|
||||
context.l10n.forgotPassword,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
),
|
||||
|
@ -139,11 +140,13 @@ class _RecoveryPageState extends State<RecoveryPage> {
|
|||
child: Center(
|
||||
child: Text(
|
||||
"No recovery key?",
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart';
|
|||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
import 'package:ente_auth/ui/common/dynamic_fab.dart';
|
||||
import "package:ente_auth/utils/crypto_util.dart";
|
||||
import "package:ente_auth/utils/dialog_util.dart";
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:flutter_sodium/flutter_sodium.dart";
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
typedef OnPasswordVerifiedFn = Future<void> Function(Uint8List bytes);
|
||||
|
@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget {
|
|||
final OnPasswordVerifiedFn onPasswordVerified;
|
||||
final Function? onPasswordError;
|
||||
|
||||
const RequestPasswordVerificationPage(
|
||||
{super.key, required this.onPasswordVerified, this.onPasswordError,});
|
||||
const RequestPasswordVerificationPage({
|
||||
super.key,
|
||||
required this.onPasswordVerified,
|
||||
this.onPasswordError,
|
||||
});
|
||||
|
||||
@override
|
||||
State<RequestPasswordVerificationPage> createState() =>
|
||||
|
@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState
|
|||
try {
|
||||
final attributes = Configuration.instance.getKeyAttributes()!;
|
||||
final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey(
|
||||
utf8.encode(_passwordController.text) as Uint8List,
|
||||
Sodium.base642bin(attributes.kekSalt),
|
||||
utf8.encode(_passwordController.text),
|
||||
CryptoUtil.base642bin(attributes.kekSalt),
|
||||
attributes.memLimit,
|
||||
attributes.opsLimit,
|
||||
);
|
||||
CryptoUtil.decryptSync(
|
||||
Sodium.base642bin(attributes.encryptedKey),
|
||||
CryptoUtil.base642bin(attributes.encryptedKey),
|
||||
keyEncryptionKey,
|
||||
Sodium.base642bin(attributes.keyDecryptionNonce),
|
||||
CryptoUtil.base642bin(attributes.keyDecryptionNonce),
|
||||
);
|
||||
dialog.show();
|
||||
// pop
|
||||
|
|
|
@ -11,7 +11,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:logging/logging.dart';
|
||||
|
||||
class SessionsPage extends StatefulWidget {
|
||||
const SessionsPage({Key? key}) : super(key: key);
|
||||
const SessionsPage({super.key});
|
||||
|
||||
@override
|
||||
State<SessionsPage> createState() => _SessionsPageState();
|
||||
|
|
|
@ -12,12 +12,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart';
|
|||
import 'package:ente_auth/ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/navigation_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sodium/flutter_sodium.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class VerifyRecoveryPage extends StatefulWidget {
|
||||
const VerifyRecoveryPage({Key? key}) : super(key: key);
|
||||
const VerifyRecoveryPage({super.key});
|
||||
|
||||
@override
|
||||
State<VerifyRecoveryPage> createState() => _VerifyRecoveryPageState();
|
||||
|
@ -34,14 +35,14 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
|||
try {
|
||||
final String inputKey = _recoveryKey.text.trim();
|
||||
final String recoveryKey =
|
||||
Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey);
|
||||
if (inputKey == recoveryKey || inputKey == recoveryKeyWords) {
|
||||
try {
|
||||
await UserRemoteFlagService.instance.markRecoveryVerificationAsDone();
|
||||
} catch (e) {
|
||||
await dialog.hide();
|
||||
if (e is DioError && e.type == DioErrorType.other) {
|
||||
if (e is DioException && e.type == DioExceptionType.unknown) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
"No internet connection",
|
||||
|
@ -88,10 +89,13 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
|
|||
context,
|
||||
"Please authenticate to view your recovery key",
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
recoveryKey =
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
|
|
|
@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget {
|
|||
final int period;
|
||||
|
||||
CodeTimerProgress({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.period,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
_CodeTimerProgressState createState() => _CodeTimerProgressState();
|
||||
State createState() => _CodeTimerProgressState();
|
||||
}
|
||||
|
||||
class _CodeTimerProgressState extends State<CodeTimerProgress>
|
||||
|
|
|
@ -14,6 +14,7 @@ import 'package:ente_auth/store/code_store.dart';
|
|||
import 'package:ente_auth/ui/code_timer_progress.dart';
|
||||
import 'package:ente_auth/ui/utils/icon_utils.dart';
|
||||
import 'package:ente_auth/utils/dialog_util.dart';
|
||||
import 'package:ente_auth/utils/platform_util.dart';
|
||||
import 'package:ente_auth/utils/toast_util.dart';
|
||||
import 'package:ente_auth/utils/totp_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -24,7 +25,7 @@ import 'package:move_to_background/move_to_background.dart';
|
|||
class CodeWidget extends StatefulWidget {
|
||||
final Code code;
|
||||
|
||||
const CodeWidget(this.code, {Key? key}) : super(key: key);
|
||||
const CodeWidget(this.code, {super.key});
|
||||
|
||||
@override
|
||||
State<CodeWidget> createState() => _CodeWidgetState();
|
||||
|
@ -372,9 +373,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
Future<void> _onEditPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.editCodeAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
final Code? code = await Navigator.of(context).push(
|
||||
|
@ -390,9 +392,10 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
Future<void> _onShowQrPressed(_) async {
|
||||
bool _isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
bool isAuthSuccessful = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(context, context.l10n.showQRAuthMessage);
|
||||
if (!_isAuthSuccessful) {
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
// ignore: unused_local_variable
|
||||
|
@ -406,14 +409,15 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
}
|
||||
|
||||
void _onDeletePressed(_) async {
|
||||
bool _isAuthSuccessful =
|
||||
bool isAuthSuccessful =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.deleteCodeAuthMessage,
|
||||
);
|
||||
if (!_isAuthSuccessful) {
|
||||
if (!isAuthSuccessful) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus();
|
||||
final l10n = context.l10n;
|
||||
await showChoiceActionSheet(
|
||||
context,
|
||||
|
@ -450,7 +454,7 @@ class _CodeWidgetState extends State<CodeWidget> {
|
|||
code = code.replaceAll(RegExp(r'\d'), '•');
|
||||
}
|
||||
if (code.length == 6) {
|
||||
return code.substring(0, 3) + " " + code.substring(3, 6);
|
||||
return "${code.substring(0, 3)} ${code.substring(3, 6)}";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
|
|
@ -5,8 +5,7 @@ import 'package:flutter/material.dart';
|
|||
class BottomShadowWidget extends StatelessWidget {
|
||||
final double offsetDy;
|
||||
final Color? shadowColor;
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
|
||||
: super(key: key);
|
||||
const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -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) {
|
|
@ -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!),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
|
|
|
@ -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<LinearProgressDialog> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
title: Text(
|
||||
widget.message,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<RenameDialog> createState() => _RenameDialogState();
|
||||
|
|
|
@ -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<WebPage> createState() => _WebPageState();
|
||||
|
@ -28,9 +28,9 @@ class _WebPageState extends State<WebPage> {
|
|||
),
|
||||
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(() {
|
||||
|
|
|
@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget {
|
|||
final ValueNotifier<String>? 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<ButtonChildWidget> createState() => _ButtonChildWidgetState();
|
||||
|
|
|
@ -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<IconButtonWidget> createState() => _IconButtonWidgetState();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ExpandableMenuItemWidget> createState() =>
|
||||
|
|
|
@ -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<HomeHeaderWidget> createState() => _HomeHeaderWidgetState();
|
||||
|
@ -16,7 +13,7 @@ class HomeHeaderWidget extends StatefulWidget {
|
|||
class _HomeHeaderWidgetState extends State<HomeHeaderWidget> {
|
||||
@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(
|
||||
|
|
|
@ -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<TrailingWidget> 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) {
|
||||
|
|
|
@ -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<MenuItemWidget> createState() => _MenuItemWidgetState();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ToggleSwitchWidget> createState() => _ToggleSwitchWidgetState();
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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<void>(
|
||||
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<void>(
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -6,8 +6,8 @@ class SpeedDialLabelWidget extends StatelessWidget {
|
|||
|
||||
const SpeedDialLabelWidget(
|
||||
this.label, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -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<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
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<ScaffoldState>();
|
||||
|
||||
final TextEditingController _textController = TextEditingController();
|
||||
bool _showSearchBox = false;
|
||||
|
@ -144,27 +149,24 @@ class _HomePageState extends State<HomePage> {
|
|||
@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<HomePage> {
|
|||
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<HomePage> {
|
|||
|
||||
Future<bool> _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<HomePage> {
|
|||
}
|
||||
|
||||
// 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<HomePage> {
|
|||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<ScannerGoogleAuthPage> createState() => ScannerGoogleAuthPageState();
|
||||
|
@ -85,7 +85,7 @@ class ScannerGoogleAuthPageState extends State<ScannerGoogleAuthPage> {
|
|||
} catch (e) {
|
||||
controller.dispose();
|
||||
Navigator.of(context).pop();
|
||||
showToast(context, "Error " + e.toString());
|
||||
showToast(context, "Error $e");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<ScannerPage> createState() => ScannerPageState();
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<AppUpdateDialog> createState() => _AppUpdateDialogState();
|
||||
|
@ -30,7 +30,7 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
|
|||
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<AppUpdateDialog> {
|
|||
);
|
||||
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<AppUpdateDialog> {
|
|||
class ApkDownloaderDialog extends StatefulWidget {
|
||||
final LatestVersionInfo? versionInfo;
|
||||
|
||||
const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
|
||||
const ApkDownloaderDialog(this.versionInfo, {super.key});
|
||||
|
||||
@override
|
||||
State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
|
||||
|
@ -131,17 +131,15 @@ class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
|
|||
@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...",
|
||||
|
|
|
@ -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<AppVersionWidget> createState() => _AppVersionWidgetState();
|
||||
|
@ -48,7 +48,7 @@ class _AppVersionWidgetState extends State<AppVersionWidget> {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Text(
|
||||
"Version: " + snapshot.data!,
|
||||
"Version: ${snapshot.data!}",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<void> _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<void> _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<void> _showExportWarningDialog(BuildContext context) async {
|
|||
Future<void> _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<String> _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;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ Future<int?> _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<int?> _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'];
|
||||
|
|
|
@ -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<void> showEncryptedImportInstruction(BuildContext context) async {
|
||||
|
@ -80,21 +79,21 @@ Future<void> _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;
|
||||
}
|
||||
|
|
|
@ -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<void> 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,
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -11,9 +11,9 @@ Future<void> 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,
|
||||
);
|
||||
|
|
|
@ -48,10 +48,8 @@ class PlainTextImport extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Future<void> showImportInstructionDialog(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
final AlertDialog alert = AlertDialog(
|
||||
|
@ -94,7 +92,6 @@ Future<void> showImportInstructionDialog(BuildContext context) async {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
Future<void> _pickImportFile(BuildContext context) async {
|
||||
final l10n = context.l10n;
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||
|
|
|
@ -170,8 +170,8 @@ Future<int?> _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);
|
|
@ -21,7 +21,9 @@ enum ImportType {
|
|||
}
|
||||
|
||||
class ImportCodePage extends StatelessWidget {
|
||||
final List<ImportType> importOptions = [
|
||||
const ImportCodePage({super.key});
|
||||
|
||||
static const List<ImportType> 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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<AdvancedSectionWidget> createState() => _AdvancedSectionWidgetState();
|
||||
|
|
|
@ -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<ItemsWidget> createState() => _ItemsWidgetState();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<SecuritySectionWidget> createState() => _SecuritySectionWidgetState();
|
||||
|
@ -78,11 +78,12 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
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<SecuritySectionWidget> {
|
|||
);
|
||||
final isEmailMFAEnabled =
|
||||
UserService.instance.hasEmailMFAEnabled();
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (hasAuthenticated) {
|
||||
await updateEmailMFA(!isEmailMFAEnabled);
|
||||
if (mounted) {
|
||||
|
@ -136,6 +138,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
context,
|
||||
context.l10n.authToViewYourActiveSessions,
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (hasAuthenticated) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
|
@ -167,6 +170,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
|||
context.l10n.lockScreenEnablePreSteps,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
FocusScope.of(context).requestFocus();
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue