diff --git a/auth/.gitignore b/auth/.gitignore index b909f8341..bcfd8fe6b 100644 --- a/auth/.gitignore +++ b/auth/.gitignore @@ -15,6 +15,11 @@ *.iws .idea/ +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ @@ -32,3 +37,4 @@ lib/generated_plugin_registrant.dart !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages android/key.properties +dist/ \ No newline at end of file diff --git a/auth/android/app/build.gradle b/auth/android/app/build.gradle index 7462f4d36..5621b08b6 100644 --- a/auth/android/app/build.gradle +++ b/auth/android/app/build.gradle @@ -32,7 +32,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -46,7 +46,7 @@ android { defaultConfig { applicationId "io.ente.auth" - minSdkVersion 20 + minSdkVersion 21 targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -56,7 +56,7 @@ android { signingConfigs { release { - storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : file(System.getenv("SIGNING_KEY_PATH")) + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : System.getenv("SIGNING_KEY_PATH") ? file(System.getenv("SIGNING_KEY_PATH")) : null keyAlias keystoreProperties['keyAlias'] ? keystoreProperties['keyAlias'] : System.getenv("SIGNING_KEY_ALIAS") keyPassword keystoreProperties['keyPassword'] ? keystoreProperties['keyPassword'] : System.getenv("SIGNING_KEY_PASSWORD") storePassword keystoreProperties['storePassword'] ? keystoreProperties['storePassword'] : System.getenv("SIGNING_STORE_PASSWORD") @@ -109,6 +109,7 @@ dependencies { implementation 'io.sentry:sentry-android:2.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.android.support:multidex:1.0.3' + implementation 'com.google.guava:guava:28.2-android' implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' diff --git a/auth/distribute_options.yaml b/auth/distribute_options.yaml new file mode 100644 index 000000000..9634f4cbd --- /dev/null +++ b/auth/distribute_options.yaml @@ -0,0 +1,25 @@ +output: dist/ + +releases: + - name: dev + jobs: + - name: release-dev-linux-zip + package: + platform: linux + target: zip + - name: release-dev-linux-deb + package: + platform: linux + target: deb + - name: release-dev-linux-appimage + package: + platform: linux + target: appimage + - name: release-dev-windows-exe + package: + platform: windows + target: exe + - name: release-dev-macos-dmg + package: + platform: macos + target: dmg diff --git a/auth/ios/Podfile.lock b/auth/ios/Podfile.lock index dc7d8d043..c70156377 100644 --- a/auth/ios/Podfile.lock +++ b/auth/ios/Podfile.lock @@ -1,7 +1,9 @@ PODS: - - connectivity (0.0.1): + - app_links (0.0.1): - Flutter - - Reachability + - connectivity_plus (0.0.1): + - Flutter + - ReachabilitySwift - device_info_plus (0.0.1): - Flutter - DKImagePickerController/Core (4.3.4): @@ -45,27 +47,24 @@ PODS: - Flutter (1.0.0) - flutter_email_sender (0.0.1): - Flutter - - flutter_inappwebview (0.0.1): + - flutter_inappwebview_ios (0.0.1): - Flutter - - flutter_inappwebview/Core (= 0.0.1) + - flutter_inappwebview_ios/Core (= 0.0.1) - OrderedSet (~> 5.0) - - flutter_inappwebview/Core (0.0.1): + - flutter_inappwebview_ios/Core (0.0.1): - Flutter - OrderedSet (~> 5.0) + - flutter_local_authentication (1.2.0): + - Flutter - flutter_local_notifications (0.0.1): - Flutter - flutter_native_splash (0.0.1): - Flutter - flutter_secure_storage (6.0.0): - Flutter - - flutter_sodium (0.0.1): - - Flutter - fluttertoast (0.0.2): - Flutter - Toast - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - local_auth_ios (0.0.1): - Flutter - move_to_background (0.0.1): @@ -84,45 +83,63 @@ PODS: - qr_code_scanner (0.2.0): - Flutter - MTBBarcodeScanner - - Reachability (3.2) - - SDWebImage (5.17.0): - - SDWebImage/Core (= 5.17.0) - - SDWebImage/Core (5.17.0) - - Sentry/HybridSDK (8.9.1): - - SentryPrivate (= 8.9.1) + - ReachabilitySwift (5.0.0) + - SDWebImage (5.18.10): + - SDWebImage/Core (= 5.18.10) + - SDWebImage/Core (5.18.10) + - Sentry/HybridSDK (8.19.0): + - SentryPrivate (= 8.19.0) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 8.9.1) - - SentryPrivate (8.9.1) + - Sentry/HybridSDK (= 8.19.0) + - SentryPrivate (8.19.0) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - smart_auth (0.0.1): + - Flutter + - sodium_libs (2.2.0): + - Flutter - sqflite (0.0.3): - Flutter - - FMDB (>= 2.7.5) - - SwiftyGif (5.4.4) - - Toast (4.0.0) - - uni_links (0.0.1): + - FlutterMacOS + - sqlite3 (3.45.1): + - sqlite3/common (= 3.45.1) + - sqlite3/common (3.45.1) + - sqlite3/fts5 (3.45.1): + - sqlite3/common + - sqlite3/perf-threadsafe (3.45.1): + - sqlite3/common + - sqlite3/rtree (3.45.1): + - sqlite3/common + - sqlite3_flutter_libs (0.0.1): - Flutter + - sqlite3 (~> 3.45.1) + - sqlite3/fts5 + - sqlite3/perf-threadsafe + - sqlite3/rtree + - SwiftyGif (5.4.4) + - Toast (4.1.0) - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: - - connectivity (from `.symlinks/plugins/connectivity/ios`) + - app_links (from `.symlinks/plugins/app_links/ios`) + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) - fk_user_agent (from `.symlinks/plugins/fk_user_agent/ios`) - Flutter (from `Flutter`) - flutter_email_sender (from `.symlinks/plugins/flutter_email_sender/ios`) - - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) + - flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - - flutter_sodium (from `.symlinks/plugins/flutter_sodium/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`) - move_to_background (from `.symlinks/plugins/move_to_background/ios`) @@ -134,27 +151,31 @@ DEPENDENCIES: - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/ios`) - - uni_links (from `.symlinks/plugins/uni_links/ios`) + - smart_auth (from `.symlinks/plugins/smart_auth/ios`) + - sodium_libs (from `.symlinks/plugins/sodium_libs/ios`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: - DKImagePickerController - DKPhotoGallery - - FMDB - MTBBarcodeScanner - OrderedSet - - Reachability + - ReachabilitySwift - SDWebImage - Sentry - SentryPrivate + - sqlite3 - SwiftyGif - Toast EXTERNAL SOURCES: - connectivity: - :path: ".symlinks/plugins/connectivity/ios" + app_links: + :path: ".symlinks/plugins/app_links/ios" + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: @@ -167,16 +188,16 @@ EXTERNAL SOURCES: :path: Flutter flutter_email_sender: :path: ".symlinks/plugins/flutter_email_sender/ios" - flutter_inappwebview: - :path: ".symlinks/plugins/flutter_inappwebview/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" + flutter_local_authentication: + :path: ".symlinks/plugins/flutter_local_authentication/ios" flutter_local_notifications: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" - flutter_sodium: - :path: ".symlinks/plugins/flutter_sodium/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" local_auth_ios: @@ -199,51 +220,58 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + smart_auth: + :path: ".symlinks/plugins/smart_auth/ios" + sodium_libs: + :path: ".symlinks/plugins/sodium_libs/ios" sqflite: - :path: ".symlinks/plugins/sqflite/ios" - uni_links: - :path: ".symlinks/plugins/uni_links/ios" + :path: ".symlinks/plugins/sqflite/darwin" + sqlite3_flutter_libs: + :path: ".symlinks/plugins/sqlite3_flutter_libs/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467 - device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875 + connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d + device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: ce3938a0df3cc1ef404671531facef740d03f920 + file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b - flutter_inappwebview: acd4fc0f012cefd09015000c241137d82f01ba62 - flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 + flutter_inappwebview_ios: 97215cf7d4677db55df76782dbd2930c5e1c1ea0 + flutter_local_authentication: 1172a4dd88f6306dadce067454e2c4caf07977bb + flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be - flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - local_auth_ios: c6cf091ded637a88f24f86a8875d8b0f526e2605 + local_auth_ios: 1ba1475238daa33a6ffa2a29242558437be435ac move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e qr_code_scanner: bb67d64904c3b9658ada8c402e8b4d406d5d796e - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - SDWebImage: 750adf017a315a280c60fde706ab1e552a3ae4e9 - Sentry: e3203780941722a1fcfee99e351de14244c7f806 - sentry_flutter: 8f0ffd53088e6a4d50c095852c5cad9e4405025c - SentryPrivate: 5e3683390f66611fc7c6215e27645873adb55d13 + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + SDWebImage: fc8f2d48bbfd72ef39d70e981bd24a3f3be53fec + Sentry: 1ebcaef678a27c8ac515f974cb5425dd1bbdec2f + sentry_flutter: ecdfbedee55337205561cfa782ee02d31ec83e1f + SentryPrivate: 765c9b4ebe9ac1a5fcdc067c5a1cfbf3f10e1677 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + smart_auth: 4bedbc118723912d0e45a07e8ab34039c19e04f2 + sodium_libs: 0486eb2c3172ce494406367d4b379042444b769d + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + sqlite3: 73b7fc691fdc43277614250e04d183740cb15078 + sqlite3_flutter_libs: af0e8fe9bce48abddd1ffdbbf839db0302d72d80 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + Toast: ec33c32b8688982cecc6348adeae667c1b9938da + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: b4e3a7eabb03395b66e81fc061789f61526ee6bb diff --git a/auth/ios/Runner/AppDelegate.swift b/auth/ios/Runner/AppDelegate.swift index 70693e4a8..3fdfc7389 100644 --- a/auth/ios/Runner/AppDelegate.swift +++ b/auth/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ -import UIKit import Flutter +import UIKit +import app_links @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { @@ -8,6 +9,15 @@ import Flutter didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) - return super.application(application, didFinishLaunchingWithOptions: launchOptions) + + super.application(application, didFinishLaunchingWithOptions: launchOptions) + + if let url = AppLinks.shared.getLink(launchOptions: launchOptions) { + AppLinks.shared.handleLink(url: url) + } + + return false + + // return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/auth/ios/Runner/Info.plist b/auth/ios/Runner/Info.plist index 5d24c4cee..28984c705 100644 --- a/auth/ios/Runner/Info.plist +++ b/auth/ios/Runner/Info.plist @@ -77,5 +77,9 @@ UIViewControllerBasedStatusBarAppearance + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + diff --git a/auth/lib/app/view/app.dart b/auth/lib/app/view/app.dart index b723e54b9..c8e9051e6 100644 --- a/auth/lib/app/view/app.dart +++ b/auth/lib/app/view/app.dart @@ -20,7 +20,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; class App extends StatefulWidget { final Locale locale; - const App({Key? key, this.locale = const Locale("en")}) : super(key: key); + const App({super.key, this.locale = const Locale("en")}); static void setLocale(BuildContext context, Locale newLocale) { _AppState state = context.findAncestorStateOfType<_AppState>()!; diff --git a/auth/lib/core/configuration.dart b/auth/lib/core/configuration.dart index 3e6540af6..0dc84ba12 100644 --- a/auth/lib/core/configuration.dart +++ b/auth/lib/core/configuration.dart @@ -12,12 +12,12 @@ import 'package:ente_auth/models/key_attributes.dart'; import 'package:ente_auth/models/key_gen_result.dart'; import 'package:ente_auth/models/private_key_attributes.dart'; import 'package:ente_auth/store/authenticator_db.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:tuple/tuple.dart'; class Configuration { @@ -70,9 +70,10 @@ class Configuration { Future init() async { _preferences = await SharedPreferences.getInstance(); + sqfliteFfiInit(); _secureStorage = const FlutterSecureStorage(); _documentsDirectory = (await getApplicationDocumentsDirectory()).path; - _tempDirectory = _documentsDirectory + "/temp/"; + _tempDirectory = "$_documentsDirectory/temp/"; final tempDirectory = io.Directory(_tempDirectory); try { final currentTime = DateTime.now().microsecondsSinceEpoch; @@ -160,7 +161,7 @@ class Configuration { // decrypt the master key final kekSalt = CryptoUtil.getSaltToDeriveKey(); final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( - utf8.encode(password) as Uint8List, + utf8.encode(password), kekSalt, ); final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); @@ -170,28 +171,28 @@ class Configuration { CryptoUtil.encryptSync(masterKey, derivedKeyResult.key); // Generate a public-private keypair and encrypt the latter - final keyPair = await CryptoUtil.generateKeyPair(); + final keyPair = CryptoUtil.generateKeyPair(); final encryptedSecretKeyData = - CryptoUtil.encryptSync(keyPair.sk, masterKey); + CryptoUtil.encryptSync(keyPair.secretKey.extractBytes(), masterKey); final attributes = KeyAttributes( - Sodium.bin2base64(kekSalt), - Sodium.bin2base64(encryptedKeyData.encryptedData!), - Sodium.bin2base64(encryptedKeyData.nonce!), - Sodium.bin2base64(keyPair.pk), - Sodium.bin2base64(encryptedSecretKeyData.encryptedData!), - Sodium.bin2base64(encryptedSecretKeyData.nonce!), + CryptoUtil.bin2base64(kekSalt), + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedKeyData.nonce!), + CryptoUtil.bin2base64(keyPair.publicKey), + CryptoUtil.bin2base64(encryptedSecretKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedSecretKeyData.nonce!), derivedKeyResult.memLimit, derivedKeyResult.opsLimit, - Sodium.bin2base64(encryptedMasterKey.encryptedData!), - Sodium.bin2base64(encryptedMasterKey.nonce!), - Sodium.bin2base64(encryptedRecoveryKey.encryptedData!), - Sodium.bin2base64(encryptedRecoveryKey.nonce!), + CryptoUtil.bin2base64(encryptedMasterKey.encryptedData!), + CryptoUtil.bin2base64(encryptedMasterKey.nonce!), + CryptoUtil.bin2base64(encryptedRecoveryKey.encryptedData!), + CryptoUtil.bin2base64(encryptedRecoveryKey.nonce!), ); final privateAttributes = PrivateKeyAttributes( - Sodium.bin2base64(masterKey), - Sodium.bin2hex(recoveryKey), - Sodium.bin2base64(keyPair.sk), + CryptoUtil.bin2base64(masterKey), + CryptoUtil.bin2hex(recoveryKey), + CryptoUtil.bin2base64(keyPair.secretKey.extractBytes()), ); return KeyGenResult(attributes, privateAttributes, loginKey); } @@ -206,7 +207,7 @@ class Configuration { // decrypt the master key final kekSalt = CryptoUtil.getSaltToDeriveKey(); final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( - utf8.encode(password) as Uint8List, + utf8.encode(password), kekSalt, ); final loginKey = await CryptoUtil.deriveLoginKey(derivedKeyResult.key); @@ -218,9 +219,9 @@ class Configuration { final existingAttributes = getKeyAttributes(); final updatedAttributes = existingAttributes!.copyWith( - kekSalt: Sodium.bin2base64(kekSalt), - encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!), - keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!), + kekSalt: CryptoUtil.bin2base64(kekSalt), + encryptedKey: CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + keyDecryptionNonce: CryptoUtil.bin2base64(encryptedKeyData.nonce!), memLimit: derivedKeyResult.memLimit, opsLimit: derivedKeyResult.opsLimit, ); @@ -238,8 +239,8 @@ class Configuration { }) async { _logger.info('Start decryptAndSaveSecrets'); keyEncryptionKey ??= await CryptoUtil.deriveKey( - utf8.encode(password) as Uint8List, - Sodium.base642bin(attributes.kekSalt), + utf8.encode(password), + CryptoUtil.base642bin(attributes.kekSalt), attributes.memLimit, attributes.opsLimit, ); @@ -248,31 +249,31 @@ class Configuration { Uint8List key; try { key = CryptoUtil.decryptSync( - Sodium.base642bin(attributes.encryptedKey), + CryptoUtil.base642bin(attributes.encryptedKey), keyEncryptionKey, - Sodium.base642bin(attributes.keyDecryptionNonce), + CryptoUtil.base642bin(attributes.keyDecryptionNonce), ); } catch (e) { _logger.severe('master-key failed, incorrect password?', e); throw Exception("Incorrect password"); } _logger.info("master-key done"); - await setKey(Sodium.bin2base64(key)); + await setKey(CryptoUtil.bin2base64(key)); final secretKey = CryptoUtil.decryptSync( - Sodium.base642bin(attributes.encryptedSecretKey), + CryptoUtil.base642bin(attributes.encryptedSecretKey), key, - Sodium.base642bin(attributes.secretKeyDecryptionNonce), + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), ); _logger.info("secret-key done"); - await setSecretKey(Sodium.bin2base64(secretKey)); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); final token = CryptoUtil.openSealSync( - Sodium.base642bin(getEncryptedToken()!), - Sodium.base642bin(attributes.publicKey), + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), secretKey, ); _logger.info('appToken done'); await setToken( - Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe), + CryptoUtil.bin2base64(token, urlSafe: true), ); return keyEncryptionKey; } @@ -291,28 +292,28 @@ class Configuration { Uint8List masterKey; try { masterKey = await CryptoUtil.decrypt( - Sodium.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), - Sodium.hex2bin(recoveryKey), - Sodium.base642bin(attributes.masterKeyDecryptionNonce), + CryptoUtil.base642bin(attributes!.masterKeyEncryptedWithRecoveryKey), + CryptoUtil.hex2bin(recoveryKey), + CryptoUtil.base642bin(attributes.masterKeyDecryptionNonce), ); } catch (e) { _logger.severe(e); rethrow; } - await setKey(Sodium.bin2base64(masterKey)); + await setKey(CryptoUtil.bin2base64(masterKey)); final secretKey = CryptoUtil.decryptSync( - Sodium.base642bin(attributes.encryptedSecretKey), + CryptoUtil.base642bin(attributes.encryptedSecretKey), masterKey, - Sodium.base642bin(attributes.secretKeyDecryptionNonce), + CryptoUtil.base642bin(attributes.secretKeyDecryptionNonce), ); - await setSecretKey(Sodium.bin2base64(secretKey)); + await setSecretKey(CryptoUtil.bin2base64(secretKey)); final token = CryptoUtil.openSealSync( - Sodium.base642bin(getEncryptedToken()!), - Sodium.base642bin(attributes.publicKey), + CryptoUtil.base642bin(getEncryptedToken()!), + CryptoUtil.base642bin(attributes.publicKey), secretKey, ); await setToken( - Sodium.bin2base64(token, variant: Sodium.base64VariantUrlsafe), + CryptoUtil.bin2base64(token, urlSafe: true), ); } @@ -400,27 +401,31 @@ class Configuration { } Uint8List? getKey() { - return _key == null ? null : Sodium.base642bin(_key!); + return _key == null ? null : CryptoUtil.base642bin(_key!); } Uint8List? getSecretKey() { - return _secretKey == null ? null : Sodium.base642bin(_secretKey!); + return _secretKey == null ? null : CryptoUtil.base642bin(_secretKey!); } Uint8List? getAuthSecretKey() { - return _authSecretKey == null ? null : Sodium.base642bin(_authSecretKey!); + return _authSecretKey == null + ? null + : CryptoUtil.base642bin(_authSecretKey!); } Uint8List? getOfflineSecretKey() { - return _offlineAuthKey == null ? null : Sodium.base642bin(_offlineAuthKey!); + return _offlineAuthKey == null + ? null + : CryptoUtil.base642bin(_offlineAuthKey!); } Uint8List getRecoveryKey() { final keyAttributes = getKeyAttributes()!; return CryptoUtil.decryptSync( - Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), + CryptoUtil.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey), getKey()!, - Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce), + CryptoUtil.base642bin(keyAttributes.recoveryKeyDecryptionNonce), ); } @@ -447,7 +452,7 @@ class Configuration { iOptions: _secureStorageOptionsIOS, ); } else { - _offlineAuthKey = Sodium.bin2base64(CryptoUtil.generateKey()); + _offlineAuthKey = CryptoUtil.bin2base64(CryptoUtil.generateKey()); await _secureStorage.write( key: offlineAuthSecretKey, value: _offlineAuthKey, diff --git a/auth/lib/core/constants.dart b/auth/lib/core/constants.dart index dcc111c4b..de39eb301 100644 --- a/auth/lib/core/constants.dart +++ b/auth/lib/core/constants.dart @@ -7,7 +7,7 @@ const String sentryDSN = "https://ed4ddd6309b847ba8849935e26e9b648@sentry.ente.io/9"; const String sentryTunnel = "https://sentry-reporter.ente.io"; const String roadmapURL = "https://roadmap.ente.io"; -const String githubDiscussionsUrl = "https://github.com/ente-io/ente/discussions"; +const String githubIssuesUrl = "https://github.com/ente-io/auth/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc"; const int microSecondsInDay = 86400000000; const int android11SDKINT = 30; const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748 diff --git a/auth/lib/core/errors.dart b/auth/lib/core/errors.dart index c1e83cde0..ba1310b6c 100644 --- a/auth/lib/core/errors.dart +++ b/auth/lib/core/errors.dart @@ -1,9 +1,9 @@ class InvalidFileError extends ArgumentError { - InvalidFileError(String message) : super(message); + InvalidFileError(String super.message); } class InvalidFileUploadState extends AssertionError { - InvalidFileUploadState(String message) : super(message); + InvalidFileUploadState(String super.message); } class SubscriptionAlreadyClaimedError extends Error {} @@ -30,19 +30,15 @@ class UnauthorizedError extends Error {} class RequestCancelledError extends Error {} class InvalidSyncStatusError extends AssertionError { - InvalidSyncStatusError(String message) : super(message); + InvalidSyncStatusError(String super.message); } class UnauthorizedEditError extends AssertionError {} class InvalidStateError extends AssertionError { - InvalidStateError(String message) : super(message); + InvalidStateError(String super.message); } -class KeyDerivationError extends Error {} - -class LoginKeyDerivationError extends Error {} - class SrpSetupNotCompleteError extends Error {} class AuthenticatorKeyNotFound extends Error {} diff --git a/auth/lib/core/logging/super_logging.dart b/auth/lib/core/logging/super_logging.dart index 7b62d0a70..224ba553a 100644 --- a/auth/lib/core/logging/super_logging.dart +++ b/auth/lib/core/logging/super_logging.dart @@ -235,14 +235,14 @@ class SuperLogging { extraLines = null; } - final str = (config.prefix) + " " + rec.toPrettyString(extraLines); + final str = "${config.prefix} ${rec.toPrettyString(extraLines)}"; // write to stdout printLog(str); // push to log queue if (fileIsEnabled) { - fileQueueEntries.add(str + '\n'); + fileQueueEntries.add('$str\n'); if (fileQueueEntries.length == 1) { flushQueue(); } @@ -275,7 +275,7 @@ class SuperLogging { static var logChunkSize = 800; static void printLog(String text) { - text.chunked(logChunkSize).forEach(print); + text.chunked(logChunkSize).forEach(debugPrint); } /// A queue to be consumed by [setupSentry]. @@ -354,7 +354,7 @@ class SuperLogging { final date = config.dateFmt!.parse(basename(file.path)); dates[file as File] = date; files.add(file); - } on FormatException {} + } on Exception catch (_) {} } final nowTime = DateTime.now(); @@ -374,7 +374,7 @@ class SuperLogging { "deleting log file ${file.path}", ); await file.delete(); - } catch (ignore) {} + } on Exception catch (_) {} } } diff --git a/auth/lib/core/logging/tunneled_transport.dart b/auth/lib/core/logging/tunneled_transport.dart index 1191adc2e..f9ffd5ad1 100644 --- a/auth/lib/core/logging/tunneled_transport.dart +++ b/auth/lib/core/logging/tunneled_transport.dart @@ -46,7 +46,7 @@ class TunneledTransport implements Transport { _options.logger( SentryLevel.error, 'API returned an error, statusCode = ${response.statusCode}, ' - 'body = ${response.body}', + 'body = ${response.body}', ); } return const SentryId.empty(); @@ -65,8 +65,8 @@ class TunneledTransport implements Transport { } Future _createStreamedRequest( - SentryEnvelope envelope, - ) async { + SentryEnvelope envelope, + ) async { final streamedRequest = StreamedRequest('POST', _tunnel); envelope .envelopeStream(_options) @@ -91,10 +91,10 @@ class _CredentialBuilder { _clock = clock; factory _CredentialBuilder( - Dsn? dsn, - String sdkIdentifier, - ClockProvider clock, - ) { + Dsn? dsn, + String sdkIdentifier, + ClockProvider clock, + ) { final authHeader = _buildAuthHeader( publicKey: dsn?.publicKey, secretKey: dsn?.secretKey, diff --git a/auth/lib/core/network.dart b/auth/lib/core/network.dart index d901268d6..087d6acca 100644 --- a/auth/lib/core/network.dart +++ b/auth/lib/core/network.dart @@ -3,9 +3,10 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/constants.dart'; +import 'package:ente_auth/utils/package_info_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:fk_user_agent/fk_user_agent.dart'; import 'package:flutter/foundation.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:uuid/uuid.dart'; @@ -21,16 +22,22 @@ class Network { late Dio _enteDio; Future init() async { - await FkUserAgent.init(); - final packageInfo = await PackageInfo.fromPlatform(); + if (PlatformUtil.isMobile()) await FkUserAgent.init(); + final packageInfo = await PackageInfoUtil().getPackageInfo(); + final version = PackageInfoUtil().getVersion(packageInfo); + final packageName = PackageInfoUtil().getPackageName(packageInfo); + final preferences = await SharedPreferences.getInstance(); + _dio = Dio( BaseOptions( - connectTimeout: kConnectTimeout, + connectTimeout: Duration(milliseconds: kConnectTimeout), headers: { - HttpHeaders.userAgentHeader: FkUserAgent.userAgent, - 'X-Client-Version': packageInfo.version, - 'X-Client-Package': packageInfo.packageName, + HttpHeaders.userAgentHeader: PlatformUtil.isMobile() + ? FkUserAgent.userAgent + : Platform.operatingSystem, + 'X-Client-Version': version, + 'X-Client-Package': packageName, }, ), ); @@ -38,11 +45,14 @@ class Network { _enteDio = Dio( BaseOptions( baseUrl: apiEndpoint, - connectTimeout: kConnectTimeout, + connectTimeout: Duration(milliseconds: kConnectTimeout), headers: { - HttpHeaders.userAgentHeader: FkUserAgent.userAgent, - 'X-Client-Version': packageInfo.version, - 'X-Client-Package': packageInfo.packageName, + if (PlatformUtil.isMobile()) + HttpHeaders.userAgentHeader: FkUserAgent.userAgent + else + HttpHeaders.userAgentHeader: Platform.operatingSystem, + 'X-Client-Version': version, + 'X-Client-Package': packageName, }, ), ); @@ -76,8 +86,8 @@ class EnteRequestInterceptor extends Interceptor { void onRequest(RequestOptions options, RequestInterceptorHandler handler) { if (kDebugMode) { assert( - options.baseUrl == enteEndpoint, - "interceptor should only be used for API endpoint", + options.baseUrl == enteEndpoint, + "interceptor should only be used for API endpoint", ); } // ignore: prefer_const_constructors diff --git a/auth/lib/ente_theme_data.dart b/auth/lib/ente_theme_data.dart index bb2a994c4..057504cbd 100644 --- a/auth/lib/ente_theme_data.dart +++ b/auth/lib/ente_theme_data.dart @@ -11,6 +11,7 @@ final lightThemeData = ThemeData( iconTheme: const IconThemeData(color: Colors.black), primaryIconTheme: const IconThemeData(color: Colors.red, opacity: 1.0, size: 50.0), + buttonTheme: const ButtonThemeData(), outlinedButtonTheme: buildOutlinedButtonThemeData( bgDisabled: const Color.fromRGBO(158, 158, 158, 1), bgEnabled: const Color.fromRGBO(0, 0, 0, 1), @@ -72,24 +73,42 @@ final lightThemeData = ThemeData( ? const Color.fromRGBO(255, 255, 255, 1) : const Color.fromRGBO(0, 0, 0, 1); }), - ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - ), colorScheme: const ColorScheme.light( + ), + radioTheme: RadioThemeData( + fillColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + trackColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + colorScheme: const ColorScheme.light( primary: Colors.black, secondary: Color.fromARGB(255, 163, 163, 163), ).copyWith(background: const Color.fromRGBO(255, 255, 255, 1)), @@ -105,6 +124,7 @@ final darkThemeData = ThemeData( hintColor: const Color.fromRGBO(158, 158, 158, 1), buttonTheme: const ButtonThemeData().copyWith( buttonColor: const Color.fromRGBO(45, 194, 98, 1.0), + height: 56, ), textTheme: _buildTextTheme(const Color.fromRGBO(255, 255, 255, 1)), outlinedButtonTheme: buildOutlinedButtonThemeData( @@ -164,24 +184,43 @@ final darkThemeData = ThemeData( return const Color.fromRGBO(158, 158, 158, 1); } }), - ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return const Color.fromRGBO(102, 187, 106, 1); } - return null; - }), - ), colorScheme: const ColorScheme.dark(primary: Colors.white).copyWith(background: const Color.fromRGBO(0, 0, 0, 1)), + ), + radioTheme: RadioThemeData( + fillColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + trackColor: + MaterialStateProperty.resolveWith((Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return const Color.fromRGBO(102, 187, 106, 1); + } + return null; + }), + ), + colorScheme: const ColorScheme.dark(primary: Colors.white) + .copyWith(background: const Color.fromRGBO(0, 0, 0, 1)), ); TextTheme _buildTextTheme(Color textColor) { @@ -400,6 +439,7 @@ OutlinedButtonThemeData buildOutlinedButtonThemeData({ shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), + fixedSize: const Size.fromHeight(56), alignment: Alignment.center, padding: const EdgeInsets.fromLTRB(50, 16, 50, 16), textStyle: const TextStyle( @@ -436,7 +476,9 @@ ElevatedButtonThemeData buildElevatedButtonThemeData({ }) { return ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - foregroundColor: onPrimary, backgroundColor: primary, elevation: elevation, + foregroundColor: onPrimary, + backgroundColor: primary, + elevation: elevation, alignment: Alignment.center, textStyle: const TextStyle( fontWeight: FontWeight.w600, diff --git a/auth/lib/gateway/authenticator.dart b/auth/lib/gateway/authenticator.dart index 1ea1bee32..c51b84db5 100644 --- a/auth/lib/gateway/authenticator.dart +++ b/auth/lib/gateway/authenticator.dart @@ -10,12 +10,12 @@ class AuthenticatorGateway { late String _basedEndpoint; AuthenticatorGateway(this._dio, this._config) { - _basedEndpoint = _config.getHttpEndpoint() + "/authenticator"; + _basedEndpoint = "${_config.getHttpEndpoint()}/authenticator"; } Future createKey(String encKey, String header) async { await _dio.post( - _basedEndpoint + "/key", + "$_basedEndpoint/key", data: { "encryptedKey": encKey, "header": header, @@ -31,7 +31,7 @@ class AuthenticatorGateway { Future getKey() async { try { final response = await _dio.get( - _basedEndpoint + "/key", + "$_basedEndpoint/key", options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -39,7 +39,7 @@ class AuthenticatorGateway { ), ); return AuthKey.fromMap(response.data); - } on DioError catch (e) { + } on DioException catch (e) { if (e.response != null && (e.response!.statusCode ?? 0) == 404) { throw AuthenticatorKeyNotFound(); } else { @@ -52,7 +52,7 @@ class AuthenticatorGateway { Future createEntity(String encryptedData, String header) async { final response = await _dio.post( - _basedEndpoint + "/entity", + "$_basedEndpoint/entity", data: { "encryptedData": encryptedData, "header": header, @@ -72,7 +72,7 @@ class AuthenticatorGateway { String header, ) async { await _dio.put( - _basedEndpoint + "/entity", + "$_basedEndpoint/entity", data: { "id": id, "encryptedData": encryptedData, @@ -90,7 +90,7 @@ class AuthenticatorGateway { String id, ) async { await _dio.delete( - _basedEndpoint + "/entity", + "$_basedEndpoint/entity", queryParameters: { "id": id, }, @@ -105,7 +105,7 @@ class AuthenticatorGateway { Future> getDiff(int sinceTime, {int limit = 500}) async { try { final response = await _dio.get( - _basedEndpoint + "/entity/diff", + "$_basedEndpoint/entity/diff", queryParameters: { "sinceTime": sinceTime, "limit": limit, @@ -124,7 +124,7 @@ class AuthenticatorGateway { } return authEntities; } catch (e) { - if (e is DioError && e.response?.statusCode == 401) { + if (e is DioException && e.response?.statusCode == 401) { throw UnauthorizedError(); } else { rethrow; diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb index da9332daa..e2572b7ca 100644 --- a/auth/lib/l10n/arb/app_en.arb +++ b/auth/lib/l10n/arb/app_en.arb @@ -59,7 +59,7 @@ } }, "contactSupport": "Contact support", - "rateUsOnStore" : "Rate us on {storeName}", + "rateUsOnStore": "Rate us on {storeName}", "blog": "Blog", "merchandise": "Merchandise", "verifyPassword": "Verify password", @@ -133,7 +133,6 @@ "faq_q_5": "How can I enable FaceID lock in ente Auth", "faq_a_5": "You can enable FaceID lock under Settings → Security → Lockscreen.", "somethingWentWrongMessage": "Something went wrong, please try again", - "leaveFamily": "Leave family", "leaveFamilyMessage": "Are you sure that you want to leave the family plan?", "inFamilyPlanMessage": "You are on a family plan!", @@ -198,6 +197,10 @@ "recoveryKeySaveDescription": "We don't store this key, please save this 24 word key in a safe place.", "doThisLater": "Do this later", "saveKey": "Save key", + "save": "Save", + "send": "Send", + "saveOrSendDescription": "Do you want to save this to your storage (Downloads folder by default) or send it to other apps?", + "saveOnlyDescription": "Do you want to save this to your storage (Downloads folder by default)?", "back": "Back", "createAccount": "Create account", "passwordStrength": "Password strength: {passwordStrengthValue}", @@ -337,10 +340,10 @@ "offlineModeWarning": "You have chosen to proceed without backups. Please take manual backups to make sure your codes are safe.", "showLargeIcons": "Show large icons", "shouldHideCode": "Hide codes", - "doubleTapToViewHiddenCode" : "You can double tap on an entry to view code", + "doubleTapToViewHiddenCode": "You can double tap on an entry to view code", "focusOnSearchBar": "Focus search on app start", "confirmUpdatingkey": "Are you sure you want to update the secret key?", - "minimizeAppOnCopy": "Minimize app on copy", + "minimizeAppOnCopy": "Minimize app on copy", "editCodeAuthMessage": "Authenticate to edit code", "deleteCodeAuthMessage": "Authenticate to delete code", "showQRAuthMessage": "Authenticate to show QR code", @@ -405,5 +408,6 @@ "signOutOtherDevices": "Sign out other devices", "doNotSignOut": "Do not sign out", "hearUsWhereTitle": "How did you hear about Ente? (optional)", - "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!" -} + "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!", + "recoveryKeySaved": "Recovery key saved in Downloads folder!" +} \ No newline at end of file diff --git a/auth/lib/main.dart b/auth/lib/main.dart index 1dc7ff688..15fcab4a4 100644 --- a/auth/lib/main.dart +++ b/auth/lib/main.dart @@ -1,5 +1,6 @@ +import 'dart:io'; + import 'package:adaptive_theme/adaptive_theme.dart'; -import 'package:computer/computer.dart'; import "package:ente_auth/app/view/app.dart"; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/constants.dart'; @@ -18,7 +19,9 @@ import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/tools/app_lock.dart'; import 'package:ente_auth/ui/tools/lock_screen.dart'; import 'package:ente_auth/ui/utils/icon_utils.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_auth/utils/window_protocol_handler.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; import 'package:flutter/scheduler.dart'; @@ -26,14 +29,27 @@ import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:privacy_screen/privacy_screen.dart'; +import 'package:window_manager/window_manager.dart'; final _logger = Logger("main"); void main() async { WidgetsFlutterBinding.ensureInitialized(); + + await windowManager.ensureInitialized(); + + if (PlatformUtil.isDesktop()) { + WindowOptions windowOptions = const WindowOptions( + size: Size(450, 800), + ); + windowManager.waitUntilReadyToShow(windowOptions, () async { + await windowManager.show(); + await windowManager.focus(); + }); + } await _runInForeground(); await _setupPrivacyScreen(); - FlutterDisplayMode.setHighRefreshRate(); + if (Platform.isAndroid) FlutterDisplayMode.setHighRefreshRate(); } Future _runInForeground() async { @@ -65,10 +81,14 @@ ThemeMode _themeMode(AdaptiveThemeMode? savedThemeMode) { } Future _runWithLogs(Function() function, {String prefix = ""}) async { + String dir = ""; + try { + dir = "${(await getApplicationSupportDirectory()).path}/logs"; + } catch (_) {} await SuperLogging.main( LogConfig( body: function, - logDirPath: (await getApplicationSupportDirectory()).path + "/logs", + logDirPath: dir, maxLogFiles: 5, sentryDsn: sentryDSN, enableInDebugMode: true, @@ -77,10 +97,19 @@ Future _runWithLogs(Function() function, {String prefix = ""}) async { ); } +void _registerWindowsProtocol() { + const kWindowsScheme = 'ente'; + // Register our protocol only on Windows platform + if (!kIsWeb && Platform.isWindows) { + WindowsProtocolHandler() + .register(kWindowsScheme, executable: null, arguments: null); + } +} + Future _init(bool bool, {String? via}) async { - // Start workers asynchronously. No need to wait for them to start - Computer.shared().turnOn(workersCount: 4, verbose: kDebugMode); - CryptoUtil.init(); + _registerWindowsProtocol(); + await initCryptoUtil(); + await PreferenceService.instance.init(); await CodeStore.instance.init(); await Configuration.instance.init(); @@ -95,6 +124,7 @@ Future _init(bool bool, {String? via}) async { } Future _setupPrivacyScreen() async { + if (!PlatformUtil.isMobile()) return; final brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness; bool isInDarkMode = brightness == Brightness.dark; diff --git a/auth/lib/models/code.dart b/auth/lib/models/code.dart index 40c8c6455..fb6289d63 100644 --- a/auth/lib/models/code.dart +++ b/auth/lib/models/code.dart @@ -57,14 +57,7 @@ class Code { updatedAlgo, updatedType, updatedCounter, - "otpauth://${updatedType.name}/" + - updateIssuer + - ":" + - updateAccount + - "?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=" + - updateIssuer + - "&period=$updatePeriod&secret=" + - updatedSecret + (updatedType == Type.hotp ? "&counter=$updatedCounter" : ""), + "otpauth://${updatedType.name}/$updateIssuer:$updateAccount?algorithm=${updatedAlgo.name}&digits=$updatedDigits&issuer=$updateIssuer&period=$updatePeriod&secret=$updatedSecret${updatedType == Type.hotp ? "&counter=$updatedCounter" : ""}", generatedID: generatedID, ); } @@ -83,14 +76,7 @@ class Code { Algorithm.sha1, Type.totp, 0, - "otpauth://totp/" + - issuer + - ":" + - account + - "?algorithm=SHA1&digits=6&issuer=" + - issuer + - "&period=30&secret=" + - secret, + "otpauth://totp/$issuer:$account?algorithm=SHA1&digits=6&issuer=$issuer&period=30&secret=$secret", ); } diff --git a/auth/lib/models/derived_key_result.dart b/auth/lib/models/derived_key_result.dart deleted file mode 100644 index a071fb1f8..000000000 --- a/auth/lib/models/derived_key_result.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'dart:typed_data'; - -class DerivedKeyResult { - final Uint8List key; - final int memLimit; - final int opsLimit; - - DerivedKeyResult(this.key, this.memLimit, this.opsLimit); -} diff --git a/auth/lib/models/encryption_result.dart b/auth/lib/models/encryption_result.dart deleted file mode 100644 index 9da16c573..000000000 --- a/auth/lib/models/encryption_result.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:typed_data'; - -class EncryptionResult { - final Uint8List? encryptedData; - final Uint8List? key; - final Uint8List? header; - final Uint8List? nonce; - - EncryptionResult({ - this.encryptedData, - this.key, - this.header, - this.nonce, - }); -} diff --git a/auth/lib/onboarding/view/onboarding_page.dart b/auth/lib/onboarding/view/onboarding_page.dart index 78bf4e589..0117bdcaf 100644 --- a/auth/lib/onboarding/view/onboarding_page.dart +++ b/auth/lib/onboarding/view/onboarding_page.dart @@ -7,26 +7,18 @@ import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/events/trigger_logout_event.dart'; import "package:ente_auth/l10n/l10n.dart"; import 'package:ente_auth/locale.dart'; -import 'package:ente_auth/theme/text_style.dart'; -import 'package:ente_auth/ui/account/email_entry_page.dart'; import 'package:ente_auth/ui/account/login_page.dart'; import 'package:ente_auth/ui/account/logout_dialog.dart'; import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/account/password_reentry_page.dart'; -import 'package:ente_auth/ui/common/gradient_button.dart'; -import 'package:ente_auth/ui/components/buttons/button_widget.dart'; -import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/settings/language_picker.dart'; -import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; -import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/foundation.dart'; import "package:flutter/material.dart"; -import 'package:local_auth/local_auth.dart'; class OnboardingPage extends StatefulWidget { - const OnboardingPage({Key? key}) : super(key: key); + const OnboardingPage({super.key}); @override State createState() => _OnboardingPageState(); @@ -56,114 +48,128 @@ class _OnboardingPageState extends State { final l10n = context.l10n; return Scaffold( body: SafeArea( - child: Center( - child: SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), - child: Column( - children: [ - Column( - children: [ - kDebugMode - ? GestureDetector( - child: const Align( - alignment: Alignment.topRight, - child: Text("Lang"), - ), - onTap: () async { - final locale = await getLocale(); - routeToPage( - context, - LanguageSelectorPage( - appSupportedLocales, - (locale) async { - await setLocale(locale); - App.setLocale(context, locale); - }, - locale, + child: SingleChildScrollView( + child: Center( + child: ConstrainedBox( + constraints: + const BoxConstraints.tightFor(height: 800, width: 450), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 40.0, + horizontal: 40, + ), + child: Column( + children: [ + Column( + children: [ + kDebugMode + ? GestureDetector( + child: const Align( + alignment: Alignment.topRight, + child: Text("Lang"), + ), + onTap: () async { + final locale = await getLocale(); + routeToPage( + context, + LanguageSelectorPage( + appSupportedLocales, + (locale) async { + await setLocale(locale); + App.setLocale(context, locale); + }, + locale, + ), + ).then((value) { + setState(() {}); + }); + }, + ) + : const SizedBox(), + Image.asset( + "assets/sheild-front-gradient.png", + width: 200, + height: 200, + ), + const SizedBox(height: 12), + const Text( + "ente", + style: TextStyle( + fontWeight: FontWeight.bold, + fontFamily: 'Montserrat', + fontSize: 42, + ), + ), + const SizedBox(height: 4), + Text( + "Authenticator", + style: Theme.of(context).textTheme.headlineMedium, + ), + const SizedBox(height: 32), + Text( + l10n.onBoardingBody, + textAlign: TextAlign.center, + style: + Theme.of(context).textTheme.titleLarge!.copyWith( + color: Colors.white38, + // color: Theme.of(context) + // .colorScheme + // .mutedTextColor, ), - ).then((value) { - setState(() {}); - }); - }, - ) - : const SizedBox(), - Image.asset( - "assets/sheild-front-gradient.png", - width: 200, - height: 200, - ), - const SizedBox(height: 12), - const Text( - "ente", - style: TextStyle( - fontWeight: FontWeight.bold, - fontFamily: 'Montserrat', - fontSize: 42, ), - ), - const SizedBox(height: 4), - Text( - "Authenticator", - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 32), - Text( - l10n.onBoardingBody, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleLarge!.copyWith( - color: Colors.white38, + ], + ), + const SizedBox(height: 100), + // TODO: Remove After Stable + // Container( + // width: double.infinity, + // padding: const EdgeInsets.symmetric(horizontal: 20), + // child: GradientButton( + // onTap: _navigateToSignUpPage, + // text: l10n.newUser, + // ), + // ), + // const SizedBox(height: 24), + Container( + height: 56, + width: double.infinity, + padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), + child: Hero( + tag: "log_in", + child: ElevatedButton( + style: Theme.of(context) + .colorScheme + .optionalActionButtonStyle, + onPressed: _navigateToSignInPage, + child: Text( + l10n.existingUser, + style: const TextStyle( + color: Colors.black, // same for both themes ), - ), - ], - ), - const SizedBox(height: 100), - Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(horizontal: 20), - child: GradientButton( - onTap: _navigateToSignUpPage, - text: l10n.newUser, - ), - ), - const SizedBox(height: 4), - Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(20, 12, 20, 0), - child: Hero( - tag: "log_in", - child: ElevatedButton( - style: Theme.of(context) - .colorScheme - .optionalActionButtonStyle, - onPressed: _navigateToSignInPage, - child: Text( - l10n.existingUser, - style: const TextStyle( - color: Colors.black, // same for both themes ), ), ), ), - ), - const SizedBox(height: 4), - Container( - width: double.infinity, - padding: const EdgeInsets.only(top: 20, bottom: 20), - child: GestureDetector( - onTap: _optForOfflineMode, - child: Center( - child: Text( - l10n.useOffline, - style: body.copyWith( - color: Theme.of(context).colorScheme.mutedTextColor, - ), - ), - ), - ), - ), - ], + const SizedBox(height: 4), + // TODO: Remove After Stable + // Container( + // width: double.infinity, + // padding: const EdgeInsets.only(top: 20, bottom: 20), + // child: GestureDetector( + // onTap: _optForOfflineMode, + // child: Center( + // child: Text( + // l10n.useOffline, + // style: body.copyWith( + // color: + // Theme.of(context).colorScheme.mutedTextColor, + // ), + // ), + // ), + // ), + // ), + ], + ), ), ), ), @@ -172,65 +178,69 @@ class _OnboardingPageState extends State { ); } - Future _optForOfflineMode() async { - bool canCheckBio = await LocalAuthentication().canCheckBiometrics; - if (!canCheckBio) { - showToast( - context, - "Sorry, biometric authentication is not supported on this device.", - ); - return; - } - final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode(); - ButtonResult? result; - if (!hasOptedBefore) { - result = await showChoiceActionSheet( - context, - title: context.l10n.warning, - body: context.l10n.offlineModeWarning, - secondButtonLabel: context.l10n.cancel, - firstButtonLabel: context.l10n.ok, - ); - } - if (hasOptedBefore || result?.action == ButtonAction.first) { - await Configuration.instance.optForOfflineMode(); - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return const HomePage(); - }, - ), - ); - } - } + // TODO: Remove After Stable + // Future _optForOfflineMode() async { + // final canContinue = Platform.isMacOS || Platform.isLinux + // ? true + // : await LocalAuthentication().canCheckBiometrics; - void _navigateToSignUpPage() { - Widget page; - if (Configuration.instance.getEncryptedToken() == null) { - page = const EmailEntryPage(); - } else { - // No key - if (Configuration.instance.getKeyAttributes() == null) { - // Never had a key - page = const PasswordEntryPage( - mode: PasswordEntryMode.set, - ); - } else if (Configuration.instance.getKey() == null) { - // Yet to decrypt the key - page = const PasswordReentryPage(); - } else { - // All is well, user just has not subscribed - page = const HomePage(); - } - } - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return page; - }, - ), - ); - } + // if (!canContinue) { + // showToast( + // context, + // "Sorry, biometric authentication is not supported on this device.", + // ); + // return; + // } + // final bool hasOptedBefore = Configuration.instance.hasOptedForOfflineMode(); + // ButtonResult? result; + // if (!hasOptedBefore) { + // result = await showChoiceActionSheet( + // context, + // title: context.l10n.warning, + // body: context.l10n.offlineModeWarning, + // secondButtonLabel: context.l10n.cancel, + // firstButtonLabel: context.l10n.ok, + // ); + // } + // if (hasOptedBefore || result?.action == ButtonAction.first) { + // await Configuration.instance.optForOfflineMode(); + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (BuildContext context) { + // return const HomePage(); + // }, + // ), + // ); + // } + // } + + // void _navigateToSignUpPage() { + // Widget page; + // if (Configuration.instance.getEncryptedToken() == null) { + // page = const EmailEntryPage(); + // } else { + // // No key + // if (Configuration.instance.getKeyAttributes() == null) { + // // Never had a key + // page = const PasswordEntryPage( + // mode: PasswordEntryMode.set, + // ); + // } else if (Configuration.instance.getKey() == null) { + // // Yet to decrypt the key + // page = const PasswordReentryPage(); + // } else { + // // All is well, user just has not subscribed + // page = const HomePage(); + // } + // } + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (BuildContext context) { + // return page; + // }, + // ), + // ); + // } void _navigateToSignInPage() { Widget page; diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart index ee46d7953..3937142d6 100644 --- a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart +++ b/auth/lib/onboarding/view/setup_enter_secret_key_page.dart @@ -9,7 +9,7 @@ import "package:flutter/material.dart"; class SetupEnterSecretKeyPage extends StatefulWidget { final Code? code; - SetupEnterSecretKeyPage({this.code, Key? key}) : super(key: key); + SetupEnterSecretKeyPage({this.code, super.key}); @override State createState() => @@ -32,7 +32,7 @@ class _SetupEnterSecretKeyPageState extends State { widget.code != null ? safeDecode(widget.code!.account).trim() : null, ); _secretController = TextEditingController( - text: widget.code != null ? widget.code!.secret : null, + text: widget.code?.secret, ); _secretKeyObscured = widget.code != null; super.initState(); @@ -45,8 +45,8 @@ class _SetupEnterSecretKeyPageState extends State { appBar: AppBar( title: Text(l10n.importAccountPageTitle), ), - body: SafeArea( - child: Center( + body: Center( + child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), child: Column( diff --git a/auth/lib/onboarding/view/view_qr_page.dart b/auth/lib/onboarding/view/view_qr_page.dart index 71f4756a7..687f810e3 100644 --- a/auth/lib/onboarding/view/view_qr_page.dart +++ b/auth/lib/onboarding/view/view_qr_page.dart @@ -1,4 +1,3 @@ - import 'dart:math'; import "package:ente_auth/l10n/l10n.dart"; @@ -10,7 +9,7 @@ import 'package:qr_flutter/qr_flutter.dart'; class ViewQrPage extends StatelessWidget { final Code? code; - ViewQrPage({this.code, Key? key}) : super(key: key); + ViewQrPage({this.code, super.key}); @override Widget build(BuildContext context) { @@ -22,15 +21,22 @@ class ViewQrPage extends StatelessWidget { appBar: AppBar( title: Text(l10n.qrCode), ), - body: SafeArea( - child: Center( + body: Center( + child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), child: Column( children: [ - QrImage( + QrImageView( data: code!.rawData, - foregroundColor: Theme.of(context).colorScheme.onBackground, + eyeStyle: QrEyeStyle( + eyeShape: QrEyeShape.square, + color: Theme.of(context).colorScheme.onBackground, + ), + dataModuleStyle: QrDataModuleStyle( + dataModuleShape: QrDataModuleShape.square, + color: Theme.of(context).colorScheme.onBackground, + ), version: QrVersions.auto, size: qrSize, ), diff --git a/auth/lib/services/authenticator_service.dart b/auth/lib/services/authenticator_service.dart index cdc9a4fb3..4cb424440 100644 --- a/auth/lib/services/authenticator_service.dart +++ b/auth/lib/services/authenticator_service.dart @@ -16,9 +16,8 @@ import 'package:ente_auth/models/authenticator/entity_result.dart'; import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; import 'package:ente_auth/store/authenticator_db.dart'; import 'package:ente_auth/store/offline_authenticator_db.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -26,6 +25,7 @@ enum AccountMode { online, offline, } + extension on AccountMode { bool get isOnline => this == AccountMode.online; bool get isOffline => this == AccountMode.offline; @@ -75,10 +75,10 @@ class AuthenticatorService { final key = await getOrCreateAuthDataKey(mode); for (LocalAuthEntity e in result) { try { - final decryptedValue = await CryptoUtil.decryptChaCha( - Sodium.base642bin(e.encryptedData), + final decryptedValue = await CryptoUtil.decryptData( + CryptoUtil.base642bin(e.encryptedData), key, - Sodium.base642bin(e.header), + CryptoUtil.base642bin(e.header), ); final hasSynced = !(e.id == null || e.shouldSync); entities.add( @@ -101,12 +101,13 @@ class AuthenticatorService { AccountMode accountMode, ) async { var key = await getOrCreateAuthDataKey(accountMode); - final encryptedKeyData = await CryptoUtil.encryptChaCha( - utf8.encode(plainText) as Uint8List, + final encryptedKeyData = await CryptoUtil.encryptData( + utf8.encode(plainText), key, ); - String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!); - String header = Sodium.bin2base64(encryptedKeyData.header!); + String encryptedData = + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); + String header = CryptoUtil.bin2base64(encryptedKeyData.header!); final insertedID = accountMode.isOnline ? await _db.insert(encryptedData, header) : await _offlineDb.insert(encryptedData, header); @@ -123,12 +124,13 @@ class AuthenticatorService { AccountMode accountMode, ) async { var key = await getOrCreateAuthDataKey(accountMode); - final encryptedKeyData = await CryptoUtil.encryptChaCha( - utf8.encode(plainText) as Uint8List, + final encryptedKeyData = await CryptoUtil.encryptData( + utf8.encode(plainText), key, ); - String encryptedData = Sodium.bin2base64(encryptedKeyData.encryptedData!); - String header = Sodium.bin2base64(encryptedKeyData.header!); + String encryptedData = + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!); + String header = CryptoUtil.bin2base64(encryptedKeyData.header!); final int affectedRows = accountMode.isOnline ? await _db.updateEntry(generatedID, encryptedData, header) : await _offlineDb.updateEntry(generatedID, encryptedData, header); @@ -154,7 +156,7 @@ class AuthenticatorService { } else { debugPrint("Skipping delete since account mode is offline"); } - if(accountMode.isOnline) { + if (accountMode.isOnline) { await _db.deleteByIDs(generatedIDs: [genID]); } else { await _offlineDb.deleteByIDs(generatedIDs: [genID]); @@ -163,7 +165,7 @@ class AuthenticatorService { Future onlineSync() async { try { - if(getAccountMode().isOffline) { + if (getAccountMode().isOffline) { debugPrint("Skipping sync since account mode is offline"); return false; } @@ -191,25 +193,25 @@ class AuthenticatorService { Future _remoteToLocalSync() async { _logger.info('Initiating remote to local sync'); final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0; - _logger.info("Current sync is " + lastSyncTime.toString()); + _logger.info("Current sync is $lastSyncTime"); const int fetchLimit = 500; final List result = await _gateway.getDiff(lastSyncTime, limit: fetchLimit); - _logger.info(result.length.toString() + " entries fetched from remote"); + _logger.info("${result.length} entries fetched from remote"); if (result.isEmpty) { return; } final maxSyncTime = result.map((e) => e.updatedAt).reduce(max); List deletedIDs = result.where((element) => element.isDeleted).map((e) => e.id).toList(); - _logger.info(deletedIDs.length.toString() + " entries deleted"); + _logger.info("${deletedIDs.length} entries deleted"); result.removeWhere((element) => element.isDeleted); await _db.insertOrReplace(result); if (deletedIDs.isNotEmpty) { await _db.deleteByIDs(ids: deletedIDs); } _prefs.setInt(_lastEntitySyncTime, maxSyncTime); - _logger.info("Setting synctime to " + maxSyncTime.toString()); + _logger.info("Setting synctime to $maxSyncTime"); if (result.length == fetchLimit) { _logger.info("Diff limit reached, pulling again"); await _remoteToLocalSync(); @@ -223,7 +225,7 @@ class AuthenticatorService { .where((element) => element.shouldSync || element.id == null) .toList(); _logger.info( - pendingUpdate.length.toString() + " entries to be updated at remote", + "${pendingUpdate.length} entries to be updated at remote", ); for (LocalAuthEntity entity in pendingUpdate) { if (entity.id == null) { @@ -253,7 +255,7 @@ class AuthenticatorService { } Future getOrCreateAuthDataKey(AccountMode mode) async { - if(mode.isOffline) { + if (mode.isOffline) { return _config.getOfflineSecretKey()!; } if (_config.getAuthSecretKey() != null) { @@ -262,21 +264,21 @@ class AuthenticatorService { try { final AuthKey response = await _gateway.getKey(); final authKey = CryptoUtil.decryptSync( - Sodium.base642bin(response.encryptedKey), + CryptoUtil.base642bin(response.encryptedKey), _config.getKey()!, - Sodium.base642bin(response.header), + CryptoUtil.base642bin(response.header), ); - await _config.setAuthSecretKey(Sodium.bin2base64(authKey)); + await _config.setAuthSecretKey(CryptoUtil.bin2base64(authKey)); return authKey; } on AuthenticatorKeyNotFound catch (e) { _logger.info("AuthenticatorKeyNotFound generating key ${e.stackTrace}"); final key = CryptoUtil.generateKey(); final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!); await _gateway.createKey( - Sodium.bin2base64(encryptedKeyData.encryptedData!), - Sodium.bin2base64(encryptedKeyData.nonce!), + CryptoUtil.bin2base64(encryptedKeyData.encryptedData!), + CryptoUtil.bin2base64(encryptedKeyData.nonce!), ); - await _config.setAuthSecretKey(Sodium.bin2base64(key)); + await _config.setAuthSecretKey(CryptoUtil.bin2base64(key)); return key; } catch (e, s) { _logger.severe("Failed to getOrCreateAuthDataKey", e, s); diff --git a/auth/lib/services/billing_service.dart b/auth/lib/services/billing_service.dart index 75a4e5579..926a11e4d 100644 --- a/auth/lib/services/billing_service.dart +++ b/auth/lib/services/billing_service.dart @@ -28,6 +28,8 @@ class BillingService { final _dio = Network.instance.getDio(); final _config = Configuration.instance; + bool _isOnSubscriptionPage = false; + Subscription? _cachedSubscription; Future? _future; @@ -50,7 +52,7 @@ class BillingService { Future> _fetchPrivateBillingPlans() { return _dio.get( - _config.getHttpEndpoint() + "/billing/user-plans/", + "${_config.getHttpEndpoint()}/billing/user-plans/", options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -60,7 +62,7 @@ class BillingService { } Future> _fetchPublicBillingPlans() { - return _dio.get(_config.getHttpEndpoint() + "/billing/plans/v2"); + return _dio.get("${_config.getHttpEndpoint()}/billing/plans/v2"); } Future verifySubscription( @@ -70,7 +72,7 @@ class BillingService { }) async { try { final response = await _dio.post( - _config.getHttpEndpoint() + "/billing/verify-subscription", + "${_config.getHttpEndpoint()}/billing/verify-subscription", data: { "paymentProvider": paymentProvider ?? (Platform.isAndroid ? "playstore" : "appstore"), @@ -84,7 +86,7 @@ class BillingService { ), ); return Subscription.fromMap(response.data["subscription"]); - } on DioError catch (e) { + } on DioException catch (e) { if (e.response != null && e.response!.statusCode == 409) { throw SubscriptionAlreadyClaimedError(); } else { @@ -100,7 +102,7 @@ class BillingService { if (_cachedSubscription == null) { try { final response = await _dio.get( - _config.getHttpEndpoint() + "/billing/subscription", + "${_config.getHttpEndpoint()}/billing/subscription", options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -109,7 +111,7 @@ class BillingService { ); _cachedSubscription = Subscription.fromMap(response.data["subscription"]); - } on DioError catch (e, s) { + } on DioException catch (e, s) { _logger.severe(e, s); rethrow; } @@ -120,7 +122,7 @@ class BillingService { Future cancelStripeSubscription() async { try { final response = await _dio.post( - _config.getHttpEndpoint() + "/billing/stripe/cancel-subscription", + "${_config.getHttpEndpoint()}/billing/stripe/cancel-subscription", options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -129,7 +131,7 @@ class BillingService { ); final subscription = Subscription.fromMap(response.data["subscription"]); return subscription; - } on DioError catch (e, s) { + } on DioException catch (e, s) { _logger.severe(e, s); rethrow; } @@ -138,7 +140,7 @@ class BillingService { Future activateStripeSubscription() async { try { final response = await _dio.post( - _config.getHttpEndpoint() + "/billing/stripe/activate-subscription", + "${_config.getHttpEndpoint()}/billing/stripe/activate-subscription", options: Options( headers: { "X-Auth-Token": _config.getToken(), @@ -147,7 +149,7 @@ class BillingService { ); final subscription = Subscription.fromMap(response.data["subscription"]); return subscription; - } on DioError catch (e, s) { + } on DioException catch (e, s) { _logger.severe(e, s); rethrow; } @@ -158,7 +160,7 @@ class BillingService { }) async { try { final response = await _dio.get( - _config.getHttpEndpoint() + "/billing/stripe/customer-portal", + "${_config.getHttpEndpoint()}/billing/stripe/customer-portal", queryParameters: { "redirectURL": kWebPaymentRedirectUrl, }, @@ -169,9 +171,13 @@ class BillingService { ), ); return response.data["url"]; - } on DioError catch (e, s) { + } on DioException catch (e, s) { _logger.severe(e, s); rethrow; } } + + void setIsOnSubscriptionPage(bool isOnSubscriptionPage) { + _isOnSubscriptionPage = isOnSubscriptionPage; + } } diff --git a/auth/lib/services/local_authentication_service.dart b/auth/lib/services/local_authentication_service.dart index d21d1bcb2..6b6889429 100644 --- a/auth/lib/services/local_authentication_service.dart +++ b/auth/lib/services/local_authentication_service.dart @@ -1,15 +1,21 @@ +import 'dart:io'; + import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/ui/tools/app_lock.dart'; import 'package:ente_auth/utils/auth_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_local_authentication/flutter_local_authentication.dart'; import 'package:local_auth/local_auth.dart'; +import 'package:logging/logging.dart'; class LocalAuthenticationService { LocalAuthenticationService._privateConstructor(); static final LocalAuthenticationService instance = LocalAuthenticationService._privateConstructor(); + final logger = Logger((LocalAuthenticationService).toString()); Future requestLocalAuthentication( BuildContext context, @@ -38,7 +44,7 @@ class LocalAuthenticationService { String errorDialogContent, [ String errorDialogTitle = "", ]) async { - if (await LocalAuthentication().isDeviceSupported()) { + if (await _isLocalAuthSupportedOnDevice()) { AppLock.of(context)!.disable(); final result = await requestAuthentication( context, @@ -64,6 +70,12 @@ class LocalAuthenticationService { } Future _isLocalAuthSupportedOnDevice() async { - return await LocalAuthentication().isDeviceSupported(); + try { + return Platform.isMacOS || Platform.isLinux + ? await FlutterLocalAuthentication().canAuthenticate() + : await LocalAuthentication().isDeviceSupported(); + } on MissingPluginException { + return false; + } } } diff --git a/auth/lib/services/notification_service.dart b/auth/lib/services/notification_service.dart index 009cce846..ce4bc536f 100644 --- a/auth/lib/services/notification_service.dart +++ b/auth/lib/services/notification_service.dart @@ -27,7 +27,7 @@ class NotificationService { _flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); if (implementation != null) { - implementation.requestPermission(); + implementation.requestNotificationsPermission(); } } diff --git a/auth/lib/services/user_remote_flag_service.dart b/auth/lib/services/user_remote_flag_service.dart index 6961518aa..74deae584 100644 --- a/auth/lib/services/user_remote_flag_service.dart +++ b/auth/lib/services/user_remote_flag_service.dart @@ -96,7 +96,7 @@ class UserRemoteFlagService { queryParams["defaultValue"] = defaultValue; } final response = await _dio.get( - _config.getHttpEndpoint() + "/remote-store", + "${_config.getHttpEndpoint()}/remote-store", queryParameters: queryParams, options: Options( headers: { @@ -119,7 +119,7 @@ class UserRemoteFlagService { Future _updateKeyValue(String key, String value) async { try { final response = await _dio.post( - _config.getHttpEndpoint() + "/remote-store/update", + "${_config.getHttpEndpoint()}/remote-store/update", data: { "key": key, "value": value, diff --git a/auth/lib/services/user_service.dart b/auth/lib/services/user_service.dart index 682412744..bc043d20d 100644 --- a/auth/lib/services/user_service.dart +++ b/auth/lib/services/user_service.dart @@ -28,9 +28,9 @@ import 'package:ente_auth/ui/common/progress_dialog.dart'; import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/ui/two_factor_authentication_page.dart'; import 'package:ente_auth/ui/two_factor_recovery_page.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -78,7 +78,7 @@ class UserService { await dialog.show(); try { final response = await _dio.post( - _config.getHttpEndpoint() + "/users/ott", + "${_config.getHttpEndpoint()}/users/ott", data: {"email": email, "purpose": isChangeEmail ? "change" : ""}, ); await dialog.hide(); @@ -100,7 +100,7 @@ class UserService { return; } unawaited(showGenericErrorDialog(context: context)); - } on DioError catch (e) { + } on DioException catch (e) { await dialog.hide(); _logger.info(e); if (e.response != null && e.response!.statusCode == 403) { @@ -127,7 +127,7 @@ class UserService { String type = "SubCancellation", }) async { await _dio.post( - _config.getHttpEndpoint() + "/anonymous/feedback", + "${_config.getHttpEndpoint()}/anonymous/feedback", data: {"feedback": feedback, "type": "type"}, ); } @@ -171,7 +171,7 @@ class UserService { try { final response = await _enteDio.get("/users/sessions"); return Sessions.fromMap(response.data); - } on DioError catch (e) { + } on DioException catch (e) { _logger.info(e); rethrow; } @@ -185,7 +185,7 @@ class UserService { "token": token, }, ); - } on DioError catch (e) { + } on DioException catch (e) { _logger.info(e); rethrow; } @@ -194,7 +194,7 @@ class UserService { Future leaveFamilyPlan() async { try { await _enteDio.delete("/family/leave"); - } on DioError catch (e) { + } on DioException catch (e) { _logger.warning('failed to leave family plan', e); rethrow; } @@ -276,11 +276,11 @@ class UserService { "ott": ott, }; if (!_config.isLoggedIn()) { - verifyData["source"] = 'auth:' + _getRefSource(); + verifyData["source"] = 'auth:${_getRefSource()}'; } try { final response = await _dio.post( - _config.getHttpEndpoint() + "/users/verify-email", + "${_config.getHttpEndpoint()}/users/verify-email", data: verifyData, ); await dialog.hide(); @@ -315,7 +315,7 @@ class UserService { // should never reach here throw Exception("unexpected response during email verification"); } - } on DioError catch (e) { + } on DioException catch (e) { _logger.info(e); await dialog.hide(); if (e.response != null && e.response!.statusCode == 410) { @@ -376,7 +376,7 @@ class UserService { context.l10n.oops, context.l10n.verificationFailedPleaseTryAgain, ); - } on DioError catch (e) { + } on DioException catch (e) { await dialog.hide(); if (e.response != null && e.response!.statusCode == 403) { showErrorDialog( @@ -423,7 +423,7 @@ class UserService { Future getSrpAttributes(String email) async { try { final response = await _dio.get( - _config.getHttpEndpoint() + "/users/srp/attributes", + "${_config.getHttpEndpoint()}/users/srp/attributes", queryParameters: { "email": email, }, @@ -433,7 +433,7 @@ class UserService { } else { throw Exception("get-srp-attributes action failed"); } - } on DioError catch (e) { + } on DioException catch (e) { if (e.response != null && e.response!.statusCode == 404) { throw SrpSetupNotCompleteError(); } @@ -486,10 +486,9 @@ class UserService { // ignore: need to calculate secret to get M1, unused_local_variable final clientS = client.calculateSecret(serverB); final clientM = client.calculateClientEvidenceMessage(); - // ignore: unused_local_variable - late Response srpCompleteResponse; + late Response _; if (setKeysRequest == null) { - srpCompleteResponse = await _enteDio.post( + _ = await _enteDio.post( "/users/srp/complete", data: { 'setupID': setupSRPResponse.setupID, @@ -497,7 +496,7 @@ class UserService { }, ); } else { - srpCompleteResponse = await _enteDio.post( + _ = await _enteDio.post( "/users/srp/update", data: { 'setupID': setupSRPResponse.setupID, @@ -536,7 +535,7 @@ class UserService { late Uint8List keyEncryptionKey; _logger.finest('Start deriving key'); keyEncryptionKey = await CryptoUtil.deriveKey( - utf8.encode(userPassword) as Uint8List, + utf8.encode(userPassword), CryptoUtil.base642bin(srpAttributes.kekSalt), srpAttributes.memLimit, srpAttributes.opsLimit, @@ -559,7 +558,7 @@ class UserService { final A = client.generateClientCredentials(salt, identity, password); final createSessionResponse = await _dio.post( - _config.getHttpEndpoint() + "/users/srp/create-session", + "${_config.getHttpEndpoint()}/users/srp/create-session", data: { "srpUserID": srpAttributes.srpUserID, "srpA": base64Encode(SRP6Util.encodeBigInt(A!)), @@ -573,7 +572,7 @@ class UserService { final clientS = client.calculateSecret(serverB); final clientM = client.calculateClientEvidenceMessage(); final response = await _dio.post( - _config.getHttpEndpoint() + "/users/srp/verify-session", + "${_config.getHttpEndpoint()}/users/srp/verify-session", data: { "sessionID": sessionID, "srpUserID": srpAttributes.srpUserID, @@ -667,7 +666,7 @@ class UserService { await dialog.show(); try { final response = await _dio.post( - _config.getHttpEndpoint() + "/users/two-factor/verify", + "${_config.getHttpEndpoint()}/users/two-factor/verify", data: { "sessionID": sessionID, "code": code, @@ -686,7 +685,7 @@ class UserService { (route) => route.isFirst, ); } - } on DioError catch (e) { + } on DioException catch (e) { await dialog.hide(); _logger.severe(e); if (e.response != null && e.response!.statusCode == 404) { @@ -722,7 +721,7 @@ class UserService { await dialog.show(); try { final response = await _dio.get( - _config.getHttpEndpoint() + "/users/two-factor/recover", + "${_config.getHttpEndpoint()}/users/two-factor/recover", queryParameters: { "sessionID": sessionID, }, @@ -741,7 +740,7 @@ class UserService { (route) => route.isFirst, ); } - } on DioError catch (e) { + } on DioException catch (e) { _logger.severe(e); if (e.response != null && e.response!.statusCode == 404) { showToast(context, context.l10n.sessionExpired); @@ -809,7 +808,7 @@ class UserService { } try { final response = await _dio.post( - _config.getHttpEndpoint() + "/users/two-factor/remove", + "${_config.getHttpEndpoint()}/users/two-factor/remove", data: { "sessionID": sessionID, "secret": secret, @@ -830,7 +829,7 @@ class UserService { (route) => route.isFirst, ); } - } on DioError catch (e) { + } on DioException catch (e) { _logger.severe(e); if (e.response != null && e.response!.statusCode == 404) { showToast(context, "Session expired"); diff --git a/auth/lib/store/authenticator_db.dart b/auth/lib/store/authenticator_db.dart index 0f05e83c0..deb57bc81 100644 --- a/auth/lib/store/authenticator_db.dart +++ b/auth/lib/store/authenticator_db.dart @@ -3,10 +3,12 @@ import 'dart:io'; import 'package:ente_auth/models/authenticator/auth_entity.dart'; import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; +import 'package:ente_auth/utils/directory_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; class AuthenticatorDB { static const _databaseName = "ente.authenticator.db"; @@ -25,6 +27,16 @@ class AuthenticatorDB { } Future _initDatabase() async { + if (Platform.isWindows || Platform.isLinux) { + var databaseFactory = databaseFactoryFfi; + return await databaseFactory.openDatabase( + await DirectoryUtils.getDatabasePath(_databaseName), + options: OpenDatabaseOptions( + version: _databaseVersion, + onCreate: _onCreate, + ), + ); + } final Directory documentsDirectory = await getApplicationDocumentsDirectory(); final String path = join(documentsDirectory.path, _databaseName); @@ -166,7 +178,7 @@ class AuthenticatorDB { batch.delete(entityTable, where: whereID, whereArgs: [id]); } } - await batch.commit(); + final _ = await batch.commit(); debugPrint("Done"); } diff --git a/auth/lib/store/offline_authenticator_db.dart b/auth/lib/store/offline_authenticator_db.dart index ed8f93fb9..d1af51fff 100644 --- a/auth/lib/store/offline_authenticator_db.dart +++ b/auth/lib/store/offline_authenticator_db.dart @@ -3,10 +3,11 @@ import 'dart:io'; import 'package:ente_auth/models/authenticator/auth_entity.dart'; import 'package:ente_auth/models/authenticator/local_auth_entity.dart'; +import 'package:ente_auth/utils/directory_utils.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:sqflite/sqflite.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart'; class OfflineAuthenticatorDB { static const _databaseName = "ente.offline_authenticator.db"; @@ -15,7 +16,8 @@ class OfflineAuthenticatorDB { static const entityTable = 'entities'; OfflineAuthenticatorDB._privateConstructor(); - static final OfflineAuthenticatorDB instance = OfflineAuthenticatorDB._privateConstructor(); + static final OfflineAuthenticatorDB instance = + OfflineAuthenticatorDB._privateConstructor(); static Future? _dbFuture; @@ -25,8 +27,18 @@ class OfflineAuthenticatorDB { } Future _initDatabase() async { + if (Platform.isWindows || Platform.isLinux) { + var databaseFactory = databaseFactoryFfi; + return await databaseFactory.openDatabase( + await DirectoryUtils.getDatabasePath(_databaseName), + options: OpenDatabaseOptions( + version: _databaseVersion, + onCreate: _onCreate, + ), + ); + } final Directory documentsDirectory = - await getApplicationDocumentsDirectory(); + await getApplicationDocumentsDirectory(); final String path = join(documentsDirectory.path, _databaseName); debugPrint(path); return await openDatabase( @@ -70,10 +82,10 @@ class OfflineAuthenticatorDB { } Future updateEntry( - int generatedID, - String encData, - String header, - ) async { + int generatedID, + String encData, + String header, + ) async { final db = await instance.database; final int timeInMicroSeconds = DateTime.now().microsecondsSinceEpoch; int affectedRows = await db.update( @@ -151,7 +163,7 @@ class OfflineAuthenticatorDB { batch.delete(entityTable, where: whereID, whereArgs: [id]); } } - await batch.commit(); + final _ = await batch.commit(); debugPrint("Done"); } diff --git a/auth/lib/store/user_store.dart b/auth/lib/store/user_store.dart index 20f3ead72..30409671b 100644 --- a/auth/lib/store/user_store.dart +++ b/auth/lib/store/user_store.dart @@ -3,7 +3,6 @@ import 'package:shared_preferences/shared_preferences.dart'; class UserStore { UserStore._privateConstructor(); - // ignore: unused_field late SharedPreferences _preferences; static final UserStore instance = UserStore._privateConstructor(); diff --git a/auth/lib/theme/colors.dart b/auth/lib/theme/colors.dart index 1d4a8517c..9ac9d2d7e 100644 --- a/auth/lib/theme/colors.dart +++ b/auth/lib/theme/colors.dart @@ -204,6 +204,7 @@ const Color _warning700 = Color.fromRGBO(234, 63, 63, 1); const Color _warning500 = Color.fromRGBO(255, 101, 101, 1); const Color _warning800 = Color(0xFFF53434); const Color warning500 = Color.fromRGBO(255, 101, 101, 1); +// ignore: unused_element const Color _warning400 = Color.fromRGBO(255, 111, 111, 1); const Color _caution500 = Color.fromRGBO(255, 194, 71, 1); diff --git a/auth/lib/ui/account/change_email_dialog.dart b/auth/lib/ui/account/change_email_dialog.dart index 7cfa10f4a..828970cd7 100644 --- a/auth/lib/ui/account/change_email_dialog.dart +++ b/auth/lib/ui/account/change_email_dialog.dart @@ -5,7 +5,7 @@ import 'package:ente_auth/utils/email_util.dart'; import 'package:flutter/material.dart'; class ChangeEmailDialog extends StatefulWidget { - const ChangeEmailDialog({Key? key}) : super(key: key); + const ChangeEmailDialog({super.key}); @override State createState() => _ChangeEmailDialogState(); diff --git a/auth/lib/ui/account/delete_account_page.dart b/auth/lib/ui/account/delete_account_page.dart index e4376c982..b6a314ec7 100644 --- a/auth/lib/ui/account/delete_account_page.dart +++ b/auth/lib/ui/account/delete_account_page.dart @@ -7,15 +7,15 @@ import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/ui/common/dialogs.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/email_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; class DeleteAccountPage extends StatelessWidget { const DeleteAccountPage({ - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { @@ -150,6 +150,8 @@ class DeleteAccountPage extends StatelessWidget { l10n.initiateAccountDeleteTitle, ); + await PlatformUtil.refocusWindows(); + if (hasAuthenticated) { final choice = await showChoiceDialogOld( context, @@ -164,8 +166,10 @@ class DeleteAccountPage extends StatelessWidget { return; } final decryptChallenge = CryptoUtil.openSealSync( - Sodium.base642bin(response.encryptedChallenge), - Sodium.base642bin(Configuration.instance.getKeyAttributes()!.publicKey), + CryptoUtil.base642bin(response.encryptedChallenge), + CryptoUtil.base642bin( + Configuration.instance.getKeyAttributes()!.publicKey, + ), Configuration.instance.getSecretKey()!, ); final challengeResponseStr = utf8.decode(decryptChallenge); diff --git a/auth/lib/ui/account/email_entry_page.dart b/auth/lib/ui/account/email_entry_page.dart index a20f6e245..f932535f7 100644 --- a/auth/lib/ui/account/email_entry_page.dart +++ b/auth/lib/ui/account/email_entry_page.dart @@ -5,7 +5,7 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/common/web_page.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -14,7 +14,7 @@ import 'package:step_progress_indicator/step_progress_indicator.dart'; import "package:styled_text/styled_text.dart"; class EmailEntryPage extends StatefulWidget { - const EmailEntryPage({Key? key}) : super(key: key); + const EmailEntryPage({super.key}); @override State createState() => _EmailEntryPageState(); @@ -427,15 +427,10 @@ class _EmailEntryPageState extends State { tags: { 'u-terms': StyledTextActionTag( (String? text, Map attrs) => - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.termsOfServicesTitle, - "https://ente.io/terms", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.termsOfServicesTitle, + "https://ente.io/terms", ), style: const TextStyle( decoration: TextDecoration.underline, @@ -443,15 +438,10 @@ class _EmailEntryPageState extends State { ), 'u-policy': StyledTextActionTag( (String? text, Map attrs) => - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.privacyPolicyTitle, - "https://ente.io/privacy", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.privacyPolicyTitle, + "https://ente.io/privacy", ), style: const TextStyle( decoration: TextDecoration.underline, @@ -494,15 +484,10 @@ class _EmailEntryPageState extends State { tags: { 'underline': StyledTextActionTag( (String? text, Map attrs) => - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.encryption, - "https://ente.io/architecture", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.encryption, + "https://ente.io/architecture", ), style: const TextStyle( decoration: TextDecoration.underline, diff --git a/auth/lib/ui/account/login_page.dart b/auth/lib/ui/account/login_page.dart index bfa50b8c2..a1c3fe727 100644 --- a/auth/lib/ui/account/login_page.dart +++ b/auth/lib/ui/account/login_page.dart @@ -6,13 +6,13 @@ import 'package:ente_auth/models/api/user/srp.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/ui/account/login_pwd_verification_page.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/common/web_page.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import "package:styled_text/styled_text.dart"; class LoginPage extends StatefulWidget { - const LoginPage({Key? key}) : super(key: key); + const LoginPage({super.key}); @override State createState() => _LoginPageState(); @@ -25,6 +25,36 @@ class _LoginPageState extends State { Color? _emailInputFieldColor; final Logger _logger = Logger('_LoginPageState'); + Future onPressed() async { + UserService.instance.setEmail(_email!); + Configuration.instance.resetVolatilePassword(); + SrpAttributes? attr; + bool isEmailVerificationEnabled = true; + try { + attr = await UserService.instance.getSrpAttributes(_email!); + isEmailVerificationEnabled = attr.isEmailMFAEnabled; + } catch (e) { + if (e is! SrpSetupNotCompleteError) { + _logger.severe('Error getting SRP attributes', e); + } + } + if (attr != null && !isEmailVerificationEnabled) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return LoginPasswordVerificationPage( + srpAttributes: attr!, + ); + }, + ), + ); + } else { + await UserService.instance + .sendOtt(context, _email!, isCreateAccountScreen: false); + } + FocusScope.of(context).unfocus(); + } + @override void initState() { _email = _config.getEmail(); @@ -60,35 +90,7 @@ class _LoginPageState extends State { isKeypadOpen: isKeypadOpen, isFormValid: _emailIsValid, buttonText: context.l10n.logInLabel, - onPressedFunction: () async { - UserService.instance.setEmail(_email!); - Configuration.instance.resetVolatilePassword(); - SrpAttributes? attr; - bool isEmailVerificationEnabled = true; - try { - attr = await UserService.instance.getSrpAttributes(_email!); - isEmailVerificationEnabled = attr.isEmailMFAEnabled; - } catch (e) { - if (e is! SrpSetupNotCompleteError) { - _logger.severe('Error getting SRP attributes', e); - } - } - if (attr != null && !isEmailVerificationEnabled) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return LoginPasswordVerificationPage( - srpAttributes: attr!, - ); - }, - ), - ); - } else { - await UserService.instance - .sendOtt(context, _email!, isCreateAccountScreen: false); - } - FocusScope.of(context).unfocus(); - }, + onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), floatingActionButtonAnimator: NoScalingAnimation(), @@ -115,6 +117,8 @@ class _LoginPageState extends State { padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: TextFormField( autofillHints: const [AutofillHints.email], + onFieldSubmitted: + _emailIsValid ? (value) => onPressed() : null, decoration: InputDecoration( fillColor: _emailInputFieldColor, filled: true, @@ -178,15 +182,10 @@ class _LoginPageState extends State { tags: { 'u-terms': StyledTextActionTag( (String? text, Map attrs) => - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.termsOfServicesTitle, - "https://ente.io/terms", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.termsOfServicesTitle, + "https://ente.io/terms", ), style: const TextStyle( decoration: TextDecoration.underline, @@ -194,15 +193,10 @@ class _LoginPageState extends State { ), 'u-policy': StyledTextActionTag( (String? text, Map attrs) => - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.privacyPolicyTitle, - "https://ente.io/privacy", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.privacyPolicyTitle, + "https://ente.io/privacy", ), style: const TextStyle( decoration: TextDecoration.underline, diff --git a/auth/lib/ui/account/login_pwd_verification_page.dart b/auth/lib/ui/account/login_pwd_verification_page.dart index 3bd39a1b7..2d2754ec1 100644 --- a/auth/lib/ui/account/login_pwd_verification_page.dart +++ b/auth/lib/ui/account/login_pwd_verification_page.dart @@ -1,6 +1,5 @@ import "package:dio/dio.dart"; import 'package:ente_auth/core/configuration.dart'; -import "package:ente_auth/core/errors.dart"; import "package:ente_auth/l10n/l10n.dart"; import "package:ente_auth/models/api/user/srp.dart"; import "package:ente_auth/services/user_service.dart"; @@ -9,6 +8,7 @@ import 'package:ente_auth/ui/common/dynamic_fab.dart'; import "package:ente_auth/ui/components/buttons/button_widget.dart"; import "package:ente_auth/utils/dialog_util.dart"; import "package:ente_auth/utils/email_util.dart"; +import "package:ente_crypto_dart/ente_crypto_dart.dart"; import 'package:flutter/material.dart'; import "package:logging/logging.dart"; @@ -19,8 +19,7 @@ import "package:logging/logging.dart"; // volatile password. class LoginPasswordVerificationPage extends StatefulWidget { final SrpAttributes srpAttributes; - const LoginPasswordVerificationPage({Key? key, required this.srpAttributes}) - : super(key: key); + const LoginPasswordVerificationPage({super.key, required this.srpAttributes}); @override State createState() => @@ -36,6 +35,11 @@ class _LoginPasswordVerificationPageState bool _passwordInFocus = false; bool _passwordVisible = false; + Future onPressed() async { + FocusScope.of(context).unfocus(); + await verifyPassword(context, _passwordController.text); + } + @override void initState() { super.initState(); @@ -77,10 +81,7 @@ class _LoginPasswordVerificationPageState isKeypadOpen: isKeypadOpen, isFormValid: _passwordController.text.isNotEmpty, buttonText: context.l10n.logInLabel, - onPressedFunction: () async { - FocusScope.of(context).unfocus(); - await verifyPassword(context, _passwordController.text); - }, + onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), floatingActionButtonAnimator: NoScalingAnimation(), @@ -101,7 +102,7 @@ class _LoginPasswordVerificationPageState password, dialog, ); - } on DioError catch (e, s) { + } on DioException catch (e, s) { await dialog.hide(); if (e.response != null && e.response!.statusCode == 401) { _logger.severe('server reject, failed verify SRP login', e, s); @@ -112,7 +113,7 @@ class _LoginPasswordVerificationPageState ); } else { _logger.severe('API failure during SRP login', e, s); - if (e.type == DioErrorType.other) { + if (e.type == DioExceptionType.unknown) { await _showContactSupportDialog( context, context.l10n.noInternetConnection, @@ -229,6 +230,9 @@ class _LoginPasswordVerificationPageState Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: TextFormField( + onFieldSubmitted: _passwordController.text.isNotEmpty + ? (_) => onPressed() + : null, key: const ValueKey("passwordInputField"), autofillHints: const [AutofillHints.password], decoration: InputDecoration( diff --git a/auth/lib/ui/account/ott_verification_page.dart b/auth/lib/ui/account/ott_verification_page.dart index 8bcaf3e34..cc9661def 100644 --- a/auth/lib/ui/account/ott_verification_page.dart +++ b/auth/lib/ui/account/ott_verification_page.dart @@ -17,8 +17,8 @@ class OTTVerificationPage extends StatefulWidget { this.isChangeEmail = false, this.isCreateAccountScreen = false, this.isResetPasswordScreen = false, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _OTTVerificationPageState(); @@ -27,6 +27,23 @@ class OTTVerificationPage extends StatefulWidget { class _OTTVerificationPageState extends State { final _verificationCodeController = TextEditingController(); + Future onPressed() async { + if (widget.isChangeEmail) { + await UserService.instance.changeEmail( + context, + widget.email, + _verificationCodeController.text, + ); + } else { + await UserService.instance.verifyEmail( + context, + _verificationCodeController.text, + isResettingPasswordScreen: widget.isResetPasswordScreen, + ); + } + FocusScope.of(context).unfocus(); + } + @override Widget build(BuildContext context) { final l10n = context.l10n; @@ -68,22 +85,9 @@ class _OTTVerificationPageState extends State { body: _getBody(), floatingActionButton: DynamicFAB( isKeypadOpen: isKeypadOpen, - isFormValid: !(_verificationCodeController.text.isEmpty), + isFormValid: _verificationCodeController.text.isNotEmpty, buttonText: l10n.verify, - onPressedFunction: () { - if (widget.isChangeEmail) { - UserService.instance.changeEmail( - context, - widget.email, - _verificationCodeController.text, - ); - } else { - UserService.instance - .verifyEmail(context, _verificationCodeController.text, - isResettingPasswordScreen: widget.isResetPasswordScreen,); - } - FocusScope.of(context).unfocus(); - }, + onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), floatingActionButtonAnimator: NoScalingAnimation(), @@ -160,6 +164,9 @@ class _OTTVerificationPageState extends State { padding: const EdgeInsets.fromLTRB(20, 16, 20, 16), child: TextFormField( style: Theme.of(context).textTheme.titleMedium, + onFieldSubmitted: _verificationCodeController.text.isNotEmpty + ? (_) => onPressed() + : null, decoration: InputDecoration( filled: true, hintText: l10n.tapToEnterCode, diff --git a/auth/lib/ui/account/password_entry_page.dart b/auth/lib/ui/account/password_entry_page.dart index db86b1186..e7714fb4a 100644 --- a/auth/lib/ui/account/password_entry_page.dart +++ b/auth/lib/ui/account/password_entry_page.dart @@ -4,11 +4,11 @@ import 'package:ente_auth/models/key_gen_result.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/ui/account/recovery_key_page.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import 'package:ente_auth/ui/common/web_page.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/home_page.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -25,7 +25,7 @@ enum PasswordEntryMode { class PasswordEntryPage extends StatefulWidget { final PasswordEntryMode mode; - const PasswordEntryPage({required this.mode, Key? key}) : super(key: key); + const PasswordEntryPage({required this.mode, super.key}); @override State createState() => _PasswordEntryPageState(); @@ -343,15 +343,10 @@ class _PasswordEntryPageState extends State { GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage( - context.l10n.howItWorks, - "https://ente.io/architecture", - ); - }, - ), + PlatformUtil.openWebView( + context, + context.l10n.howItWorks, + "https://ente.io/architecture", ); }, child: Container( diff --git a/auth/lib/ui/account/password_reentry_page.dart b/auth/lib/ui/account/password_reentry_page.dart index a4570c5db..7a53e4a8d 100644 --- a/auth/lib/ui/account/password_reentry_page.dart +++ b/auth/lib/ui/account/password_reentry_page.dart @@ -9,14 +9,14 @@ import 'package:ente_auth/ui/account/recovery_page.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/home_page.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/email_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class PasswordReentryPage extends StatefulWidget { - const PasswordReentryPage({Key? key}) : super(key: key); + const PasswordReentryPage({super.key}); @override State createState() => _PasswordReentryPageState(); @@ -260,8 +260,8 @@ class _PasswordReentryPageState extends State { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), - child: Wrap( - alignment: WrapAlignment.spaceBetween, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( behavior: HitTestBehavior.opaque, @@ -274,13 +274,17 @@ class _PasswordReentryPageState extends State { ), ); }, - child: Text( - context.l10n.forgotPassword, - style: - Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), + child: Center( + child: Text( + context.l10n.forgotPassword, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), + ), ), ), GestureDetector( @@ -296,13 +300,17 @@ class _PasswordReentryPageState extends State { Navigator.of(context) .popUntil((route) => route.isFirst); }, - child: Text( - context.l10n.changeEmail, - style: - Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), + child: Center( + child: Text( + context.l10n.changeEmail, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), + ), ), ), ], diff --git a/auth/lib/ui/account/recovery_key_page.dart b/auth/lib/ui/account/recovery_key_page.dart index c5ca66fdc..1b971b730 100644 --- a/auth/lib/ui/account/recovery_key_page.dart +++ b/auth/lib/ui/account/recovery_key_page.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io' as io; import 'package:bip39/bip39.dart' as bip39; @@ -7,7 +8,10 @@ import 'package:ente_auth/core/constants.dart'; import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/common/gradient_button.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:file_saver/file_saver.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:share_plus/share_plus.dart'; @@ -27,7 +31,7 @@ class RecoveryKeyPage extends StatefulWidget { const RecoveryKeyPage( this.recoveryKey, this.doneText, { - Key? key, + super.key, this.showAppBar, this.onDone, this.isDismissible, @@ -35,7 +39,7 @@ class RecoveryKeyPage extends StatefulWidget { this.text, this.subText, this.showProgressBar = false, - }) : super(key: key); + }); @override State createState() => _RecoveryKeyPageState(); @@ -44,7 +48,7 @@ class RecoveryKeyPage extends StatefulWidget { class _RecoveryKeyPageState extends State { bool _hasTriedToSave = false; final _recoveryKeyFile = io.File( - Configuration.instance.getTempDirectory() + "ente-recovery-key.txt", + "${Configuration.instance.getTempDirectory()}ente-recovery-key.txt", ); @override @@ -61,6 +65,21 @@ class _RecoveryKeyPageState extends State { ? 32 : 120; + Future copy() async { + await Clipboard.setData( + ClipboardData( + text: recoveryKey, + ), + ); + showShortToast( + context, + context.l10n.recoveryKeyCopiedToClipboard, + ); + setState(() { + _hasTriedToSave = true; + }); + } + return Scaffold( appBar: widget.showProgressBar ? AppBar( @@ -113,62 +132,73 @@ class _RecoveryKeyPageState extends State { style: Theme.of(context).textTheme.titleMedium, ), const Padding(padding: EdgeInsets.only(top: 24)), - DottedBorder( - color: const Color.fromARGB(255, 105, 17, 127), - //color of dotted/dash line - strokeWidth: 1, - //thickness of dash/dots - dashPattern: const [6, 6], - radius: const Radius.circular(8), - //dash patterns, 10 is dash width, 6 is space width - child: SizedBox( - //inner container - // height: 120, //height of inner container - width: double - .infinity, //width to 100% match to parent container. - // ignore: prefer_const_literals_to_create_immutables - child: Column( - children: [ - GestureDetector( - onTap: () async { - await Clipboard.setData( - ClipboardData(text: recoveryKey), - ); - showShortToast( - context, - context.l10n.recoveryKeyCopiedToClipboard, - ); - setState(() { - _hasTriedToSave = true; - }); - }, - child: Container( - decoration: BoxDecoration( - border: Border.all( - color: const Color.fromRGBO( - 49, - 155, - 86, - .2, - ), + Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color(0x8E9610D6), + Color(0x8E9F4FC6), + ], + stops: [0.0, 0.9753], + ), + ), + child: DottedBorder( + padding: EdgeInsets.zero, + borderType: BorderType.RRect, + strokeWidth: 1, + color: const Color(0xFF6B6B6B), + dashPattern: const [6, 6], + radius: const Radius.circular(8), + child: SizedBox( + width: double.infinity, + child: Stack( + children: [ + Column( + children: [ + Builder( + builder: (context) { + final content = Container( + padding: const EdgeInsets.all(20), + width: double.infinity, + child: Text( + recoveryKey, + textAlign: TextAlign.justify, + style: Theme.of(context) + .textTheme + .bodyLarge, + ), + ); + + if (PlatformUtil.isMobile()) { + return GestureDetector( + onTap: () async => await copy(), + child: content, + ); + } else { + return SelectableRegion( + focusNode: FocusNode(), + selectionControls: + PlatformUtil.selectionControls, + child: content, + ); + } + }, ), - borderRadius: const BorderRadius.all( - Radius.circular(2), - ), - color: Theme.of(context) - .colorScheme - .recoveryKeyBoxColor, - ), - padding: const EdgeInsets.all(20), - width: double.infinity, - child: Text( - recoveryKey, - style: - Theme.of(context).textTheme.bodyLarge, + ], + ), + Positioned( + right: 0, + top: 0, + child: PlatformCopy( + onPressed: copy, ), ), - ), - ], + ], + ), ), ), ), @@ -193,7 +223,7 @@ class _RecoveryKeyPageState extends State { ), ), ], - ), // columnEnds + ), ), ), ); @@ -207,12 +237,15 @@ class _RecoveryKeyPageState extends State { final List childrens = []; if (!_hasTriedToSave) { childrens.add( - ElevatedButton( - style: Theme.of(context).colorScheme.optionalActionButtonStyle, - onPressed: () async { - await _saveKeys(); - }, - child: Text(context.l10n.doThisLater), + SizedBox( + height: 56, + child: ElevatedButton( + style: Theme.of(context).colorScheme.optionalActionButtonStyle, + onPressed: () async { + await _saveKeys(); + }, + child: Text(context.l10n.doThisLater), + ), ), ); childrens.add(const SizedBox(height: 10)); @@ -221,19 +254,32 @@ class _RecoveryKeyPageState extends State { childrens.add( GradientButton( onTap: () async { - await _shareRecoveryKey(recoveryKey); + shareDialog( + context, + context.l10n.recoveryKey, + saveAction: () async { + await _saveRecoveryKey(recoveryKey); + }, + sendAction: () async { + await _shareRecoveryKey(recoveryKey); + }, + ); }, text: context.l10n.saveKey, ), ); + if (_hasTriedToSave) { childrens.add(const SizedBox(height: 10)); childrens.add( - ElevatedButton( - child: Text(widget.doneText), - onPressed: () async { - await _saveKeys(); - }, + SizedBox( + height: 56, + child: ElevatedButton( + child: Text(widget.doneText), + onPressed: () async { + await _saveKeys(); + }, + ), ), ); } @@ -241,11 +287,34 @@ class _RecoveryKeyPageState extends State { return childrens; } + Future _saveRecoveryKey(String recoveryKey) async { + final bytes = utf8.encode(recoveryKey); + final time = DateTime.now().millisecondsSinceEpoch; + + await PlatformUtil.shareFile( + "ente_recovery_key_$time", + "txt", + bytes, + MimeType.text, + ); + + if (mounted) { + showToast( + context, + context.l10n.recoveryKeySaved, + ); + setState(() { + _hasTriedToSave = true; + }); + } + } + Future _shareRecoveryKey(String recoveryKey) async { if (_recoveryKeyFile.existsSync()) { await _recoveryKeyFile.delete(); } _recoveryKeyFile.writeAsStringSync(recoveryKey); + // ignore: deprecated_member_use await Share.shareFiles([_recoveryKeyFile.path]); Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { @@ -264,3 +333,24 @@ class _RecoveryKeyPageState extends State { widget.onDone!(); } } + +class PlatformCopy extends StatelessWidget { + const PlatformCopy({ + super.key, + required this.onPressed, + }); + + final void Function() onPressed; + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () => onPressed(), + visualDensity: VisualDensity.compact, + icon: const Icon( + Icons.copy, + size: 16, + ), + ); + } +} diff --git a/auth/lib/ui/account/recovery_page.dart b/auth/lib/ui/account/recovery_page.dart index acfd01904..1d55fbb53 100644 --- a/auth/lib/ui/account/recovery_page.dart +++ b/auth/lib/ui/account/recovery_page.dart @@ -1,8 +1,7 @@ - - import 'dart:ui'; import 'package:ente_auth/core/configuration.dart'; +import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/common/dynamic_fab.dart'; import 'package:ente_auth/utils/dialog_util.dart'; @@ -10,7 +9,7 @@ import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; class RecoveryPage extends StatefulWidget { - const RecoveryPage({Key? key}) : super(key: key); + const RecoveryPage({super.key}); @override State createState() => _RecoveryPageState(); @@ -19,6 +18,36 @@ class RecoveryPage extends StatefulWidget { class _RecoveryPageState extends State { final _recoveryKey = TextEditingController(); + Future onPressed() async { + FocusScope.of(context).unfocus(); + final dialog = createProgressDialog(context, "Decrypting..."); + await dialog.show(); + try { + await Configuration.instance.recover(_recoveryKey.text.trim()); + await dialog.hide(); + showToast(context, "Recovery successful!"); + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (BuildContext context) { + return const PopScope( + canPop: false, + child: PasswordEntryPage( + mode: PasswordEntryMode.reset, + ), + ); + }, + ), + ); + } catch (e) { + await dialog.hide(); + String errMessage = 'The recovery key you entered is incorrect'; + if (e is AssertionError) { + errMessage = '$errMessage : ${e.message}'; + } + showErrorDialog(context, "Incorrect recovery key", errMessage); + } + } + @override Widget build(BuildContext context) { final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100; @@ -46,35 +75,7 @@ class _RecoveryPageState extends State { isKeypadOpen: isKeypadOpen, isFormValid: _recoveryKey.text.isNotEmpty, buttonText: 'Recover', - onPressedFunction: () async { - FocusScope.of(context).unfocus(); - final dialog = createProgressDialog(context, "Decrypting..."); - await dialog.show(); - try { - await Configuration.instance.recover(_recoveryKey.text.trim()); - await dialog.hide(); - showToast(context, "Recovery successful!"); - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (BuildContext context) { - return WillPopScope( - onWillPop: () async => false, - child: const PasswordEntryPage( - mode: PasswordEntryMode.reset, - ), - ); - }, - ), - ); - } catch (e) { - await dialog.hide(); - String errMessage = 'The recovery key you entered is incorrect'; - if (e is AssertionError) { - errMessage = '$errMessage : ${e.message}'; - } - showErrorDialog(context, "Incorrect recovery key", errMessage); - } - }, + onPressedFunction: onPressed, ), floatingActionButtonLocation: fabLocation(), floatingActionButtonAnimator: NoScalingAnimation(), @@ -87,7 +88,7 @@ class _RecoveryPageState extends State { padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), child: Text( - 'Forgot password', + context.l10n.forgotPassword, style: Theme.of(context).textTheme.headlineMedium, ), ), @@ -139,11 +140,13 @@ class _RecoveryPageState extends State { child: Center( child: Text( "No recovery key?", - style: - Theme.of(context).textTheme.titleMedium!.copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), ), ), ), diff --git a/auth/lib/ui/account/request_pwd_verification_page.dart b/auth/lib/ui/account/request_pwd_verification_page.dart index 02be12347..0e557bb80 100644 --- a/auth/lib/ui/account/request_pwd_verification_page.dart +++ b/auth/lib/ui/account/request_pwd_verification_page.dart @@ -5,10 +5,9 @@ import 'package:ente_auth/core/configuration.dart'; import "package:ente_auth/l10n/l10n.dart"; import "package:ente_auth/theme/ente_theme.dart"; import 'package:ente_auth/ui/common/dynamic_fab.dart'; -import "package:ente_auth/utils/crypto_util.dart"; import "package:ente_auth/utils/dialog_util.dart"; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/material.dart'; -import "package:flutter_sodium/flutter_sodium.dart"; import "package:logging/logging.dart"; typedef OnPasswordVerifiedFn = Future Function(Uint8List bytes); @@ -17,8 +16,11 @@ class RequestPasswordVerificationPage extends StatefulWidget { final OnPasswordVerifiedFn onPasswordVerified; final Function? onPasswordError; - const RequestPasswordVerificationPage( - {super.key, required this.onPasswordVerified, this.onPasswordError,}); + const RequestPasswordVerificationPage({ + super.key, + required this.onPasswordVerified, + this.onPasswordError, + }); @override State createState() => @@ -82,15 +84,15 @@ class _RequestPasswordVerificationPageState try { final attributes = Configuration.instance.getKeyAttributes()!; final Uint8List keyEncryptionKey = await CryptoUtil.deriveKey( - utf8.encode(_passwordController.text) as Uint8List, - Sodium.base642bin(attributes.kekSalt), + utf8.encode(_passwordController.text), + CryptoUtil.base642bin(attributes.kekSalt), attributes.memLimit, attributes.opsLimit, ); CryptoUtil.decryptSync( - Sodium.base642bin(attributes.encryptedKey), + CryptoUtil.base642bin(attributes.encryptedKey), keyEncryptionKey, - Sodium.base642bin(attributes.keyDecryptionNonce), + CryptoUtil.base642bin(attributes.keyDecryptionNonce), ); dialog.show(); // pop diff --git a/auth/lib/ui/account/sessions_page.dart b/auth/lib/ui/account/sessions_page.dart index 30fdc1ed0..d5e45a8e9 100644 --- a/auth/lib/ui/account/sessions_page.dart +++ b/auth/lib/ui/account/sessions_page.dart @@ -11,7 +11,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class SessionsPage extends StatefulWidget { - const SessionsPage({Key? key}) : super(key: key); + const SessionsPage({super.key}); @override State createState() => _SessionsPageState(); diff --git a/auth/lib/ui/account/verify_recovery_page.dart b/auth/lib/ui/account/verify_recovery_page.dart index dad67590b..146f3ea92 100644 --- a/auth/lib/ui/account/verify_recovery_page.dart +++ b/auth/lib/ui/account/verify_recovery_page.dart @@ -12,12 +12,13 @@ import 'package:ente_auth/ui/common/gradient_button.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:logging/logging.dart'; class VerifyRecoveryPage extends StatefulWidget { - const VerifyRecoveryPage({Key? key}) : super(key: key); + const VerifyRecoveryPage({super.key}); @override State createState() => _VerifyRecoveryPageState(); @@ -34,14 +35,14 @@ class _VerifyRecoveryPageState extends State { try { final String inputKey = _recoveryKey.text.trim(); final String recoveryKey = - Sodium.bin2hex(Configuration.instance.getRecoveryKey()); + CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); final String recoveryKeyWords = bip39.entropyToMnemonic(recoveryKey); if (inputKey == recoveryKey || inputKey == recoveryKeyWords) { try { await UserRemoteFlagService.instance.markRecoveryVerificationAsDone(); } catch (e) { await dialog.hide(); - if (e is DioError && e.type == DioErrorType.other) { + if (e is DioException && e.type == DioExceptionType.unknown) { await showErrorDialog( context, "No internet connection", @@ -88,10 +89,13 @@ class _VerifyRecoveryPageState extends State { context, "Please authenticate to view your recovery key", ); + await PlatformUtil.refocusWindows(); + if (hasAuthenticated) { String recoveryKey; try { - recoveryKey = Sodium.bin2hex(Configuration.instance.getRecoveryKey()); + recoveryKey = + CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); routeToPage( context, RecoveryKeyPage( diff --git a/auth/lib/ui/code_timer_progress.dart b/auth/lib/ui/code_timer_progress.dart index 1dbb4358e..b524a0c23 100644 --- a/auth/lib/ui/code_timer_progress.dart +++ b/auth/lib/ui/code_timer_progress.dart @@ -6,12 +6,12 @@ class CodeTimerProgress extends StatefulWidget { final int period; CodeTimerProgress({ - Key? key, + super.key, required this.period, - }) : super(key: key); + }); @override - _CodeTimerProgressState createState() => _CodeTimerProgressState(); + State createState() => _CodeTimerProgressState(); } class _CodeTimerProgressState extends State diff --git a/auth/lib/ui/code_widget.dart b/auth/lib/ui/code_widget.dart index 6494df5cf..da656803f 100644 --- a/auth/lib/ui/code_widget.dart +++ b/auth/lib/ui/code_widget.dart @@ -14,6 +14,7 @@ import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/code_timer_progress.dart'; import 'package:ente_auth/ui/utils/icon_utils.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; import 'package:flutter/material.dart'; @@ -24,7 +25,7 @@ import 'package:move_to_background/move_to_background.dart'; class CodeWidget extends StatefulWidget { final Code code; - const CodeWidget(this.code, {Key? key}) : super(key: key); + const CodeWidget(this.code, {super.key}); @override State createState() => _CodeWidgetState(); @@ -372,9 +373,10 @@ class _CodeWidgetState extends State { } Future _onEditPressed(_) async { - bool _isAuthSuccessful = await LocalAuthenticationService.instance + bool isAuthSuccessful = await LocalAuthenticationService.instance .requestLocalAuthentication(context, context.l10n.editCodeAuthMessage); - if (!_isAuthSuccessful) { + await PlatformUtil.refocusWindows(); + if (!isAuthSuccessful) { return; } final Code? code = await Navigator.of(context).push( @@ -390,9 +392,10 @@ class _CodeWidgetState extends State { } Future _onShowQrPressed(_) async { - bool _isAuthSuccessful = await LocalAuthenticationService.instance + bool isAuthSuccessful = await LocalAuthenticationService.instance .requestLocalAuthentication(context, context.l10n.showQRAuthMessage); - if (!_isAuthSuccessful) { + await PlatformUtil.refocusWindows(); + if (!isAuthSuccessful) { return; } // ignore: unused_local_variable @@ -406,14 +409,15 @@ class _CodeWidgetState extends State { } void _onDeletePressed(_) async { - bool _isAuthSuccessful = + bool isAuthSuccessful = await LocalAuthenticationService.instance.requestLocalAuthentication( context, context.l10n.deleteCodeAuthMessage, ); - if (!_isAuthSuccessful) { + if (!isAuthSuccessful) { return; } + FocusScope.of(context).requestFocus(); final l10n = context.l10n; await showChoiceActionSheet( context, @@ -450,7 +454,7 @@ class _CodeWidgetState extends State { code = code.replaceAll(RegExp(r'\d'), '•'); } if (code.length == 6) { - return code.substring(0, 3) + " " + code.substring(3, 6); + return "${code.substring(0, 3)} ${code.substring(3, 6)}"; } return code; } diff --git a/auth/lib/ui/common/bottom_shadow.dart b/auth/lib/ui/common/bottom_shadow.dart index 08a73564f..a57e5232a 100644 --- a/auth/lib/ui/common/bottom_shadow.dart +++ b/auth/lib/ui/common/bottom_shadow.dart @@ -5,8 +5,7 @@ import 'package:flutter/material.dart'; class BottomShadowWidget extends StatelessWidget { final double offsetDy; final Color? shadowColor; - const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key}) - : super(key: key); + const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, super.key}); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/common/DividerWithPadding.dart b/auth/lib/ui/common/divider_with_padding.dart similarity index 92% rename from auth/lib/ui/common/DividerWithPadding.dart rename to auth/lib/ui/common/divider_with_padding.dart index 94b20d9c2..0470bdf34 100644 --- a/auth/lib/ui/common/DividerWithPadding.dart +++ b/auth/lib/ui/common/divider_with_padding.dart @@ -1,17 +1,15 @@ - - import 'package:flutter/material.dart'; class DividerWithPadding extends StatelessWidget { final double left, top, right, bottom, thinckness; const DividerWithPadding({ - Key? key, + super.key, this.left = 0, this.top = 0, this.right = 0, this.bottom = 0, this.thinckness = 0.5, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/common/dynamic_fab.dart b/auth/lib/ui/common/dynamic_fab.dart index 0f9771627..962ede0b7 100644 --- a/auth/lib/ui/common/dynamic_fab.dart +++ b/auth/lib/ui/common/dynamic_fab.dart @@ -1,3 +1,5 @@ + + import 'dart:math' as math; import 'package:ente_auth/ente_theme_data.dart'; @@ -10,12 +12,12 @@ class DynamicFAB extends StatelessWidget { final Function? onPressedFunction; const DynamicFAB({ - Key? key, + super.key, this.isKeypadOpen, this.buttonText, this.isFormValid, this.onPressedFunction, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -60,10 +62,10 @@ class DynamicFAB extends StatelessWidget { } else { return Container( width: double.infinity, + height: 56, padding: const EdgeInsets.symmetric(horizontal: 20), child: OutlinedButton( - onPressed: - isFormValid! ? onPressedFunction as void Function()? : null, + onPressed: isFormValid! ? onPressedFunction as void Function()? : null, child: Text(buttonText!), ), ); diff --git a/auth/lib/ui/common/gradient_button.dart b/auth/lib/ui/common/gradient_button.dart index 5b021f613..8a24c6832 100644 --- a/auth/lib/ui/common/gradient_button.dart +++ b/auth/lib/ui/common/gradient_button.dart @@ -1,5 +1,3 @@ - - import 'package:flutter/material.dart'; class GradientButton extends StatelessWidget { @@ -15,17 +13,21 @@ class GradientButton extends StatelessWidget { // padding between the text and icon final double paddingValue; + // used when two icons are in row + final bool reversedGradient; + const GradientButton({ - Key? key, + super.key, this.linearGradientColors = const [ Color.fromARGB(255, 133, 44, 210), Color.fromARGB(255, 187, 26, 93), ], + this.reversedGradient = false, this.onTap, this.text = '', this.iconData, this.paddingValue = 0.0, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -71,7 +73,9 @@ class GradientButton extends StatelessWidget { gradient: LinearGradient( begin: const Alignment(0.1, -0.9), end: const Alignment(-0.6, 0.9), - colors: linearGradientColors, + colors: reversedGradient + ? linearGradientColors.reversed.toList() + : linearGradientColors, ), borderRadius: BorderRadius.circular(8), ), diff --git a/auth/lib/ui/common/linear_progress_dialog.dart b/auth/lib/ui/common/linear_progress_dialog.dart index 85bcc8c5b..08c46d6c9 100644 --- a/auth/lib/ui/common/linear_progress_dialog.dart +++ b/auth/lib/ui/common/linear_progress_dialog.dart @@ -1,12 +1,10 @@ - - import 'package:ente_auth/ente_theme_data.dart'; import 'package:flutter/material.dart'; class LinearProgressDialog extends StatefulWidget { final String message; - const LinearProgressDialog(this.message, {Key? key}) : super(key: key); + const LinearProgressDialog(this.message, {super.key}); @override LinearProgressDialogState createState() => LinearProgressDialogState(); @@ -29,8 +27,8 @@ class LinearProgressDialogState extends State { @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async => false, + return PopScope( + canPop: false, child: AlertDialog( title: Text( widget.message, diff --git a/auth/lib/ui/common/loading_widget.dart b/auth/lib/ui/common/loading_widget.dart index cf38f3e6b..d37548620 100644 --- a/auth/lib/ui/common/loading_widget.dart +++ b/auth/lib/ui/common/loading_widget.dart @@ -11,8 +11,8 @@ class EnteLoadingWidget extends StatelessWidget { this.size = 14, this.padding = 5, this.alignment = Alignment.center, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/common/progress_dialog.dart b/auth/lib/ui/common/progress_dialog.dart index 8c9a4e1f1..ecd96ae1d 100644 --- a/auth/lib/ui/common/progress_dialog.dart +++ b/auth/lib/ui/common/progress_dialog.dart @@ -152,8 +152,8 @@ class ProgressDialog { barrierColor: _barrierColor, builder: (BuildContext context) { _dismissingContext = context; - return WillPopScope( - onWillPop: () async => _barrierDismissible, + return PopScope( + canPop: _barrierDismissible, child: Dialog( backgroundColor: _backgroundColor, insetAnimationCurve: _insetAnimCurve, diff --git a/auth/lib/ui/common/rename_dialog.dart b/auth/lib/ui/common/rename_dialog.dart index 03c68d3b1..ad93d1aba 100644 --- a/auth/lib/ui/common/rename_dialog.dart +++ b/auth/lib/ui/common/rename_dialog.dart @@ -8,8 +8,7 @@ class RenameDialog extends StatefulWidget { final String type; final int maxLength; - const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100}) - : super(key: key); + const RenameDialog(this.name, this.type, {super.key, this.maxLength = 100}); @override State createState() => _RenameDialogState(); diff --git a/auth/lib/ui/common/web_page.dart b/auth/lib/ui/common/web_page.dart index 838855c7f..a714bbeb2 100644 --- a/auth/lib/ui/common/web_page.dart +++ b/auth/lib/ui/common/web_page.dart @@ -6,7 +6,7 @@ class WebPage extends StatefulWidget { final String title; final String url; - const WebPage(this.title, this.url, {Key? key}) : super(key: key); + const WebPage(this.title, this.url, {super.key}); @override State createState() => _WebPageState(); @@ -28,9 +28,9 @@ class _WebPageState extends State { ), backgroundColor: Colors.black, body: InAppWebView( - initialUrlRequest: URLRequest(url: Uri.parse(widget.url)), - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions(transparentBackground: true), + initialUrlRequest: URLRequest(url: WebUri(widget.url)), + initialSettings: InAppWebViewSettings( + transparentBackground: true, ), onLoadStop: (c, url) { setState(() { diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/auth/lib/ui/components/buttons/button_widget.dart index 28d62f4a6..1932cc02b 100644 --- a/auth/lib/ui/components/buttons/button_widget.dart +++ b/auth/lib/ui/components/buttons/button_widget.dart @@ -57,7 +57,7 @@ class ButtonWidget extends StatelessWidget { final ValueNotifier? progressStatus; const ButtonWidget({ - Key? key, + super.key, required this.buttonType, this.buttonSize = ButtonSize.large, this.icon, @@ -71,7 +71,7 @@ class ButtonWidget extends StatelessWidget { this.shouldSurfaceExecutionStates = true, this.progressStatus, this.shouldShowSuccessConfirmation = false, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -155,7 +155,7 @@ class ButtonChildWidget extends StatefulWidget { final bool shouldShowSuccessConfirmation; const ButtonChildWidget({ - Key? key, + super.key, required this.buttonStyle, required this.buttonType, required this.isDisabled, @@ -168,7 +168,7 @@ class ButtonChildWidget extends StatefulWidget { this.labelText, this.icon, this.buttonAction, - }) : super(key: key); + }); @override State createState() => _ButtonChildWidgetState(); diff --git a/auth/lib/ui/components/buttons/icon_button_widget.dart b/auth/lib/ui/components/buttons/icon_button_widget.dart index 9b0591d4c..eb5554318 100644 --- a/auth/lib/ui/components/buttons/icon_button_widget.dart +++ b/auth/lib/ui/components/buttons/icon_button_widget.dart @@ -17,7 +17,7 @@ class IconButtonWidget extends StatefulWidget { final Color? pressedColor; final Color? iconColor; const IconButtonWidget({ - Key? key, + super.key, required this.icon, required this.iconButtonType, this.disableGestureDetector = false, @@ -25,7 +25,7 @@ class IconButtonWidget extends StatefulWidget { this.defaultColor, this.pressedColor, this.iconColor, - }) : super(key: key); + }); @override State createState() => _IconButtonWidgetState(); diff --git a/auth/lib/ui/components/captioned_text_widget.dart b/auth/lib/ui/components/captioned_text_widget.dart index f03c8551f..438d906a9 100644 --- a/auth/lib/ui/components/captioned_text_widget.dart +++ b/auth/lib/ui/components/captioned_text_widget.dart @@ -13,8 +13,8 @@ class CaptionedTextWidget extends StatelessWidget { this.textStyle, this.makeTextBold = false, this.textColor, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/divider_widget.dart b/auth/lib/ui/components/divider_widget.dart index 489d59145..657eb024f 100644 --- a/auth/lib/ui/components/divider_widget.dart +++ b/auth/lib/ui/components/divider_widget.dart @@ -14,12 +14,12 @@ class DividerWidget extends StatelessWidget { final bool divColorHasBlur; final EdgeInsets? padding; const DividerWidget({ - Key? key, + super.key, required this.dividerType, this.bgColor = Colors.transparent, this.divColorHasBlur = true, this.padding, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/expandable_menu_item_widget.dart b/auth/lib/ui/components/expandable_menu_item_widget.dart index 9628078be..0d3bce928 100644 --- a/auth/lib/ui/components/expandable_menu_item_widget.dart +++ b/auth/lib/ui/components/expandable_menu_item_widget.dart @@ -13,8 +13,8 @@ class ExpandableMenuItemWidget extends StatefulWidget { required this.title, required this.selectionOptionsWidget, required this.leadingIcon, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => diff --git a/auth/lib/ui/components/home_header_widget.dart b/auth/lib/ui/components/home_header_widget.dart index 268084eef..0079cc7fa 100644 --- a/auth/lib/ui/components/home_header_widget.dart +++ b/auth/lib/ui/components/home_header_widget.dart @@ -1,13 +1,10 @@ -import 'dart:ui'; - import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/events/opened_settings_event.dart'; import 'package:flutter/material.dart'; class HomeHeaderWidget extends StatefulWidget { final Widget centerWidget; - const HomeHeaderWidget({required this.centerWidget, Key? key}) - : super(key: key); + const HomeHeaderWidget({required this.centerWidget, super.key}); @override State createState() => _HomeHeaderWidgetState(); @@ -16,7 +13,7 @@ class HomeHeaderWidget extends StatefulWidget { class _HomeHeaderWidgetState extends State { @override Widget build(BuildContext context) { - final hasNotch = window.viewPadding.top > 65; + final hasNotch = View.of(context).viewPadding.top > 65; return Padding( padding: EdgeInsets.fromLTRB(4, hasNotch ? 4 : 8, 4, 4), child: Row( diff --git a/auth/lib/ui/components/menu_item_child_widgets.dart b/auth/lib/ui/components/menu_item_child_widgets.dart index 81b59bc54..1201f5e1e 100644 --- a/auth/lib/ui/components/menu_item_child_widgets.dart +++ b/auth/lib/ui/components/menu_item_child_widgets.dart @@ -12,7 +12,7 @@ class TrailingWidget extends StatefulWidget { final double trailingExtraMargin; final bool showExecutionStates; const TrailingWidget({ - Key? key, + super.key, required this.executionStateNotifier, this.trailingIcon, this.trailingIconColor, @@ -20,7 +20,7 @@ class TrailingWidget extends StatefulWidget { required this.trailingIconIsMuted, required this.trailingExtraMargin, required this.showExecutionStates, - }) : super(key: key); + }); @override State createState() => _TrailingWidgetState(); } @@ -101,11 +101,11 @@ class ExpansionTrailingIcon extends StatelessWidget { final IconData? trailingIcon; final Color? trailingIconColor; const ExpansionTrailingIcon({ - Key? key, + super.key, required this.isExpanded, this.trailingIcon, this.trailingIconColor, - }) : super(key: key); + }); @override Widget build(BuildContext context) { @@ -138,12 +138,12 @@ class LeadingWidget extends StatelessWidget { // leadIconSize deafult value is 20. final double leadingIconSize; const LeadingWidget({ - Key? key, + super.key, required this.leadingIconSize, this.leadingIcon, this.leadingIconColor, this.leadingIconWidget, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/menu_item_widget.dart b/auth/lib/ui/components/menu_item_widget.dart index ef2249f2a..f281edd33 100644 --- a/auth/lib/ui/components/menu_item_widget.dart +++ b/auth/lib/ui/components/menu_item_widget.dart @@ -86,8 +86,8 @@ class MenuItemWidget extends StatefulWidget { this.showOnlyLoadingState = false, this.surfaceExecutionStates = true, this.alwaysShowSuccessState = false, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _MenuItemWidgetState(); diff --git a/auth/lib/ui/components/menu_section_description_widget.dart b/auth/lib/ui/components/menu_section_description_widget.dart index 6e521a536..bcbb9f2fa 100644 --- a/auth/lib/ui/components/menu_section_description_widget.dart +++ b/auth/lib/ui/components/menu_section_description_widget.dart @@ -3,8 +3,7 @@ import 'package:flutter/material.dart'; class MenuSectionDescriptionWidget extends StatelessWidget { final String content; - const MenuSectionDescriptionWidget({Key? key, required this.content}) - : super(key: key); + const MenuSectionDescriptionWidget({super.key, required this.content}); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/notification_warning_widget.dart b/auth/lib/ui/components/notification_warning_widget.dart index a799ffcd3..ab5e6dec1 100644 --- a/auth/lib/ui/components/notification_warning_widget.dart +++ b/auth/lib/ui/components/notification_warning_widget.dart @@ -21,14 +21,14 @@ class NotificationWidget extends StatelessWidget { final NotificationType type; const NotificationWidget({ - Key? key, + super.key, required this.startIcon, required this.actionIcon, required this.text, required this.onTap, this.subText, this.type = NotificationType.warning, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/title_bar_title_widget.dart b/auth/lib/ui/components/title_bar_title_widget.dart index bc20fe5b1..0aa2534aa 100644 --- a/auth/lib/ui/components/title_bar_title_widget.dart +++ b/auth/lib/ui/components/title_bar_title_widget.dart @@ -6,11 +6,11 @@ class TitleBarTitleWidget extends StatelessWidget { final bool isTitleH2; final IconData? icon; const TitleBarTitleWidget({ - Key? key, + super.key, this.title, this.isTitleH2 = false, this.icon, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/title_bar_widget.dart b/auth/lib/ui/components/title_bar_widget.dart index 8e22d4f65..16b532b96 100644 --- a/auth/lib/ui/components/title_bar_widget.dart +++ b/auth/lib/ui/components/title_bar_widget.dart @@ -14,7 +14,7 @@ class TitleBarWidget extends StatelessWidget { final bool isOnTopOfScreen; final Color? backgroundColor; const TitleBarWidget({ - Key? key, + super.key, this.leading, this.title, this.caption, @@ -25,7 +25,7 @@ class TitleBarWidget extends StatelessWidget { this.isFlexibleSpaceDisabled = false, this.isOnTopOfScreen = true, this.backgroundColor, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/components/toggle_switch_widget.dart b/auth/lib/ui/components/toggle_switch_widget.dart index 8ae99f77b..3fc0d153c 100644 --- a/auth/lib/ui/components/toggle_switch_widget.dart +++ b/auth/lib/ui/components/toggle_switch_widget.dart @@ -13,8 +13,8 @@ class ToggleSwitchWidget extends StatefulWidget { const ToggleSwitchWidget({ required this.value, required this.onChanged, - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _ToggleSwitchWidgetState(); diff --git a/auth/lib/ui/home/coach_mark_widget.dart b/auth/lib/ui/home/coach_mark_widget.dart index f628fabd4..b0e15d7d2 100644 --- a/auth/lib/ui/home/coach_mark_widget.dart +++ b/auth/lib/ui/home/coach_mark_widget.dart @@ -21,37 +21,43 @@ class CoachMarkWidget extends StatelessWidget { children: [ Expanded( child: Container( + width: double.infinity, color: Theme.of(context).colorScheme.background.withOpacity(0.1), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 8, sigmaY: 8), - child: Column( + child: Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Icon( - Icons.swipe_left, - size: 42, - ), - const SizedBox( - height: 12, - ), - Text( - l10n.swipeHint, - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - const SizedBox( - height: 16, - ), - SizedBox( - width: 160, - child: OutlinedButton( - onPressed: () async { - await PreferenceService.instance - .setHasShownCoachMark(true); - Bus.instance.fire(CodesUpdatedEvent()); - }, - child: Text(l10n.ok), - ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.swipe_left, + size: 42, + ), + const SizedBox( + height: 24, + ), + Text( + l10n.swipeHint, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox( + height: 36, + ), + SizedBox( + width: 160, + child: OutlinedButton( + onPressed: () async { + await PreferenceService.instance + .setHasShownCoachMark(true); + Bus.instance.fire(CodesUpdatedEvent()); + }, + child: Text(l10n.ok), + ), + ), + ], ), ], ), diff --git a/auth/lib/ui/home/home_empty_state.dart b/auth/lib/ui/home/home_empty_state.dart index 8a185beef..0768f0491 100644 --- a/auth/lib/ui/home/home_empty_state.dart +++ b/auth/lib/ui/home/home_empty_state.dart @@ -3,6 +3,7 @@ import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/settings/data/import_page.dart'; import 'package:ente_auth/ui/settings/faq.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/material.dart'; class HomeEmptyStateWidget extends StatelessWidget { @@ -10,86 +11,90 @@ class HomeEmptyStateWidget extends StatelessWidget { final VoidCallback? onManuallySetupTap; const HomeEmptyStateWidget({ - Key? key, + super.key, required this.onScanTap, required this.onManuallySetupTap, - }) : super(key: key); + }); @override Widget build(BuildContext context) { final l10n = context.l10n; return SingleChildScrollView( child: Center( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - children: [ - Image.asset( - "assets/wallet-front-gradient.png", - width: 200, - height: 200, - ), - Text( - l10n.setupFirstAccount, - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 64), - SizedBox( - width: 400, - child: OutlinedButton( - onPressed: onScanTap, - child: Text(l10n.importScanQrCode), + child: ConstrainedBox( + constraints: const BoxConstraints.tightFor(height: 800, width: 450), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 40.0, horizontal: 40), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + children: [ + Image.asset( + "assets/wallet-front-gradient.png", + width: 200, + height: 200, ), - ), - const SizedBox(height: 18), - SizedBox( - width: 400, - child: OutlinedButton( - onPressed: onManuallySetupTap, - child: Text(l10n.importEnterSetupKey), - ), - ), - const SizedBox(height: 54), - InkWell( - onTap: () { - routeToPage(context, ImportCodePage()); - }, - child: Text( - l10n.importCodes, + Text( + l10n.setupFirstAccount, textAlign: TextAlign.center, - style: getEnteTextTheme(context) - .bodyFaint - .copyWith(decoration: TextDecoration.underline), + style: Theme.of(context).textTheme.headlineMedium, ), - ), - const SizedBox(height: 18), - InkWell( - onTap: () { - showModalBottomSheet( - backgroundColor: - Theme.of(context).colorScheme.background, - barrierColor: Colors.black87, - context: context, - builder: (context) { - return const FAQQuestionsWidget(); - }, - ); - }, - child: Text( - l10n.faq, - textAlign: TextAlign.center, - style: getEnteTextTheme(context) - .bodyFaint - .copyWith(decoration: TextDecoration.underline), + const SizedBox(height: 64), + if (PlatformUtil.isMobile()) + SizedBox( + width: 400, + child: OutlinedButton( + onPressed: onScanTap, + child: Text(l10n.importScanQrCode), + ), + ), + const SizedBox(height: 18), + SizedBox( + width: 400, + child: OutlinedButton( + onPressed: onManuallySetupTap, + child: Text(l10n.importEnterSetupKey), + ), ), - ), - ], - ), - ], + const SizedBox(height: 54), + InkWell( + onTap: () { + routeToPage(context, const ImportCodePage()); + }, + child: Text( + l10n.importCodes, + textAlign: TextAlign.center, + style: getEnteTextTheme(context) + .bodyFaint + .copyWith(decoration: TextDecoration.underline), + ), + ), + const SizedBox(height: 18), + InkWell( + onTap: () { + showModalBottomSheet( + backgroundColor: + Theme.of(context).colorScheme.background, + barrierColor: Colors.black87, + context: context, + builder: (context) { + return const FAQQuestionsWidget(); + }, + ); + }, + child: Text( + l10n.faq, + textAlign: TextAlign.center, + style: getEnteTextTheme(context) + .bodyFaint + .copyWith(decoration: TextDecoration.underline), + ), + ), + ], + ), + ], + ), ), ), ), diff --git a/auth/lib/ui/home/speed_dial_label_widget.dart b/auth/lib/ui/home/speed_dial_label_widget.dart index 40c945893..4889ffb6a 100644 --- a/auth/lib/ui/home/speed_dial_label_widget.dart +++ b/auth/lib/ui/home/speed_dial_label_widget.dart @@ -6,8 +6,8 @@ class SpeedDialLabelWidget extends StatelessWidget { const SpeedDialLabelWidget( this.label, { - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart index b8908650f..dfa9a8376 100644 --- a/auth/lib/ui/home_page.dart +++ b/auth/lib/ui/home_page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:app_links/app_links.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/event_bus.dart'; import 'package:ente_auth/ente_theme_data.dart'; @@ -22,28 +23,32 @@ import 'package:ente_auth/ui/home/speed_dial_label_widget.dart'; import 'package:ente_auth/ui/scanner_page.dart'; import 'package:ente_auth/ui/settings_page.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/totp_util.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:logging/logging.dart'; import 'package:move_to_background/move_to_background.dart'; -import 'package:uni_links/uni_links.dart'; class HomePage extends StatefulWidget { - const HomePage({Key? key}) : super(key: key); + const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { - static final _settingsPage = SettingsPage( + late final _settingsPage = SettingsPage( emailNotifier: UserService.instance.emailValueNotifier, + scaffoldKey: scaffoldKey, ); bool _hasLoaded = false; bool _isSettingsOpen = false; final Logger _logger = Logger("HomePage"); + final scaffoldKey = GlobalKey(); final TextEditingController _textController = TextEditingController(); bool _showSearchBox = false; @@ -144,27 +149,24 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { final l10n = context.l10n; - return WillPopScope( - onWillPop: () async { + return PopScope( + onPopInvoked: (_) async { if (_isSettingsOpen) { - Navigator.pop(context); - return false; - } - if (Platform.isAndroid) { - MoveToBackground.moveTaskToBack(); - return false; - } else { - return true; + scaffoldKey.currentState!.closeDrawer(); + return; + } else if (!Platform.isAndroid) { + Navigator.of(context).pop(); + return; } + MoveToBackground.moveTaskToBack(); }, + canPop: false, child: Scaffold( + key: scaffoldKey, drawerEnableOpenDragGesture: !Platform.isAndroid, - drawer: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 428), - child: Drawer( - width: double.infinity, - child: _settingsPage, - ), + drawer: Drawer( + width: 428, + child: _settingsPage, ), onDrawerChanged: (isOpened) => _isSettingsOpen = isOpened, body: SafeArea( @@ -232,10 +234,13 @@ class _HomePageState extends State { onManuallySetupTap: _redirectToManualEntryPage, ); } else { - final list = ListView.builder( + final list = AlignedGridView.count( + crossAxisCount: (MediaQuery.sizeOf(context).width ~/ 400) + .clamp(1, double.infinity) + .toInt(), itemBuilder: ((context, index) { try { - return CodeWidget(_filteredCodes[index]); + return ClipRect(child: CodeWidget(_filteredCodes[index])); } catch (e) { return const Text("Failed"); } @@ -289,8 +294,10 @@ class _HomePageState extends State { Future _initDeepLinks() async { // Platform messages may fail, so we use a try/catch PlatformException. + final appLinks = AppLinks(); try { - final String? initialLink = await getInitialLink(); + String? initialLink; + initialLink = await appLinks.getInitialAppLinkString(); // Parse the link and warn the user, if it is not correct, // but keep in mind it could be `null`. if (initialLink != null) { @@ -306,14 +313,16 @@ class _HomePageState extends State { } // Attach a listener to the stream - linkStream.listen( - (String? link) { - _handleDeeplink(context, link); - }, - onError: (err) { - _logger.severe(err); - }, - ); + if (!kIsWeb && !Platform.isLinux) { + appLinks.stringLinkStream.listen( + (link) { + _handleDeeplink(context, link); + }, + onError: (err) { + _logger.severe(err); + }, + ); + } return false; } @@ -342,6 +351,14 @@ class _HomePageState extends State { } Widget _getFab() { + if (PlatformUtil.isDesktop()) { + return FloatingActionButton( + onPressed: () => _redirectToManualEntryPage(), + child: const Icon(Icons.add), + elevation: 8.0, + shape: const CircleBorder(), + ); + } return SpeedDial( icon: Icons.add, activeIcon: Icons.close, diff --git a/auth/lib/ui/linear_progress_widget.dart b/auth/lib/ui/linear_progress_widget.dart index 6296e5d4a..a5dbb2140 100644 --- a/auth/lib/ui/linear_progress_widget.dart +++ b/auth/lib/ui/linear_progress_widget.dart @@ -6,8 +6,8 @@ class LinearProgressWidget extends StatelessWidget { const LinearProgressWidget({ required this.color, required this.fractionOfStorage, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/scanner_gauth_page.dart b/auth/lib/ui/scanner_gauth_page.dart index 785f1d3d3..41d0a762d 100644 --- a/auth/lib/ui/scanner_gauth_page.dart +++ b/auth/lib/ui/scanner_gauth_page.dart @@ -9,7 +9,7 @@ import 'package:flutter/material.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; class ScannerGoogleAuthPage extends StatefulWidget { - const ScannerGoogleAuthPage({Key? key}) : super(key: key); + const ScannerGoogleAuthPage({super.key}); @override State createState() => ScannerGoogleAuthPageState(); @@ -85,7 +85,7 @@ class ScannerGoogleAuthPageState extends State { } catch (e) { controller.dispose(); Navigator.of(context).pop(); - showToast(context, "Error " + e.toString()); + showToast(context, "Error $e"); } }); } diff --git a/auth/lib/ui/scanner_page.dart b/auth/lib/ui/scanner_page.dart index 0159f0cfa..6a7793631 100644 --- a/auth/lib/ui/scanner_page.dart +++ b/auth/lib/ui/scanner_page.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:qr_code_scanner/qr_code_scanner.dart'; class ScannerPage extends StatefulWidget { - const ScannerPage({Key? key}) : super(key: key); + const ScannerPage({super.key}); @override State createState() => ScannerPageState(); diff --git a/auth/lib/ui/settings/about_section_widget.dart b/auth/lib/ui/settings/about_section_widget.dart index 7c7b46377..98a24d816 100644 --- a/auth/lib/ui/settings/about_section_widget.dart +++ b/auth/lib/ui/settings/about_section_widget.dart @@ -1,19 +1,19 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/update_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; -import 'package:ente_auth/ui/common/web_page.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/settings/app_update_dialog.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; class AboutSectionWidget extends StatelessWidget { - const AboutSectionWidget({Key? key}) : super(key: key); + const AboutSectionWidget({super.key}); @override Widget build(BuildContext context) { @@ -36,7 +36,7 @@ class AboutSectionWidget extends StatelessWidget { trailingIcon: Icons.chevron_right_outlined, trailingIconIsMuted: true, onTap: () async { - launchUrl(Uri.parse("https://github.com/ente-io/ente")); + launchUrl(Uri.parse("https://github.com/ente-io/auth")); }, ), sectionOptionSpacing, @@ -102,8 +102,8 @@ class AboutMenuItemWidget extends StatelessWidget { required this.title, required this.url, this.webPageTitle, - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { @@ -115,12 +115,10 @@ class AboutMenuItemWidget extends StatelessWidget { trailingIcon: Icons.chevron_right_outlined, trailingIconIsMuted: true, onTap: () async { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return WebPage(webPageTitle ?? title, url); - }, - ), + await PlatformUtil.openWebView( + context, + webPageTitle ?? title, + url, ); }, ); diff --git a/auth/lib/ui/settings/account_section_widget.dart b/auth/lib/ui/settings/account_section_widget.dart index 13e31e037..1718f276a 100644 --- a/auth/lib/ui/settings/account_section_widget.dart +++ b/auth/lib/ui/settings/account_section_widget.dart @@ -1,22 +1,20 @@ - - import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/services/local_authentication_service.dart'; import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/account/change_email_dialog.dart'; import 'package:ente_auth/ui/account/delete_account_page.dart'; -import 'package:ente_auth/ui/account/password_entry_page.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/material.dart'; class AccountSectionWidget extends StatelessWidget { - AccountSectionWidget({Key? key}) : super(key: key); + AccountSectionWidget({super.key}); @override Widget build(BuildContext context) { @@ -46,6 +44,7 @@ class AccountSectionWidget extends StatelessWidget { context, l10n.authToChangeYourEmail, ); + await PlatformUtil.refocusWindows(); if (hasAuthenticated) { showDialog( context: context, @@ -59,33 +58,34 @@ class AccountSectionWidget extends StatelessWidget { }, ), sectionOptionSpacing, - MenuItemWidget( - captionedTextWidget: CaptionedTextWidget( - title: l10n.changePassword, - ), - pressedColor: getEnteColorScheme(context).fillFaint, - trailingIcon: Icons.chevron_right_outlined, - trailingIconIsMuted: true, - onTap: () async { - final hasAuthenticated = await LocalAuthenticationService.instance - .requestLocalAuthentication( - context, - l10n.authToChangeYourPassword, - ); - if (hasAuthenticated) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return const PasswordEntryPage( - mode: PasswordEntryMode.update, - ); - }, - ), - ); - } - }, - ), - sectionOptionSpacing, + // TODO: Remove After Stable + // MenuItemWidget( + // captionedTextWidget: CaptionedTextWidget( + // title: l10n.changePassword, + // ), + // pressedColor: getEnteColorScheme(context).fillFaint, + // trailingIcon: Icons.chevron_right_outlined, + // trailingIconIsMuted: true, + // onTap: () async { + // final hasAuthenticated = await LocalAuthenticationService.instance + // .requestLocalAuthentication( + // context, + // l10n.authToChangeYourPassword, + // ); + // if (hasAuthenticated) { + // Navigator.of(context).push( + // MaterialPageRoute( + // builder: (BuildContext context) { + // return const PasswordEntryPage( + // mode: PasswordEntryMode.update, + // ); + // }, + // ), + // ); + // } + // }, + // ), + // sectionOptionSpacing, MenuItemWidget( captionedTextWidget: CaptionedTextWidget( title: context.l10n.logout, @@ -115,6 +115,7 @@ class AccountSectionWidget extends StatelessWidget { children: children, ); } + void _onLogoutTapped(BuildContext context) { showChoiceActionSheet( context, diff --git a/auth/lib/ui/settings/app_update_dialog.dart b/auth/lib/ui/settings/app_update_dialog.dart index eba357285..6fbccbe43 100644 --- a/auth/lib/ui/settings/app_update_dialog.dart +++ b/auth/lib/ui/settings/app_update_dialog.dart @@ -14,7 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart'; class AppUpdateDialog extends StatefulWidget { final LatestVersionInfo? latestVersionInfo; - const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key); + const AppUpdateDialog(this.latestVersionInfo, {super.key}); @override State createState() => _AppUpdateDialogState(); @@ -30,7 +30,7 @@ class _AppUpdateDialogState extends State { Padding( padding: const EdgeInsets.fromLTRB(8, 4, 0, 4), child: Text( - "- " + log, + "- $log", style: Theme.of(context).textTheme.bodySmall!.copyWith( fontSize: 14, ), @@ -87,8 +87,8 @@ class _AppUpdateDialogState extends State { ); final shouldForceUpdate = UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo); - return WillPopScope( - onWillPop: () async => !shouldForceUpdate, + return PopScope( + canPop: !shouldForceUpdate, child: AlertDialog( title: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -118,7 +118,7 @@ class _AppUpdateDialogState extends State { class ApkDownloaderDialog extends StatefulWidget { final LatestVersionInfo? versionInfo; - const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key); + const ApkDownloaderDialog(this.versionInfo, {super.key}); @override State createState() => _ApkDownloaderDialogState(); @@ -131,17 +131,15 @@ class _ApkDownloaderDialogState extends State { @override void initState() { super.initState(); - _saveUrl = Configuration.instance.getTempDirectory() + - "ente-" + - widget.versionInfo!.name! + - ".apk"; + _saveUrl = + "${Configuration.instance.getTempDirectory()}ente-${widget.versionInfo!.name!}.apk"; _downloadApk(); } @override Widget build(BuildContext context) { - return WillPopScope( - onWillPop: () async => false, + return PopScope( + canPop: false, child: AlertDialog( title: const Text( "Downloading...", diff --git a/auth/lib/ui/settings/app_version_widget.dart b/auth/lib/ui/settings/app_version_widget.dart index 3b15c6480..58c693f7c 100644 --- a/auth/lib/ui/settings/app_version_widget.dart +++ b/auth/lib/ui/settings/app_version_widget.dart @@ -4,8 +4,8 @@ import 'package:package_info_plus/package_info_plus.dart'; class AppVersionWidget extends StatefulWidget { const AppVersionWidget({ - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _AppVersionWidgetState(); @@ -48,7 +48,7 @@ class _AppVersionWidgetState extends State { return Padding( padding: const EdgeInsets.all(20), child: Text( - "Version: " + snapshot.data!, + "Version: ${snapshot.data!}", style: Theme.of(context).textTheme.bodySmall, ), ); diff --git a/auth/lib/ui/settings/danger_section_widget.dart b/auth/lib/ui/settings/danger_section_widget.dart index bdf16a167..ae9953917 100644 --- a/auth/lib/ui/settings/danger_section_widget.dart +++ b/auth/lib/ui/settings/danger_section_widget.dart @@ -11,7 +11,7 @@ import 'package:ente_auth/utils/navigation_util.dart'; import 'package:flutter/material.dart'; class DangerSectionWidget extends StatelessWidget { - const DangerSectionWidget({Key? key}) : super(key: key); + const DangerSectionWidget({super.key}); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/data/data_section_widget.dart b/auth/lib/ui/settings/data/data_section_widget.dart index b30bf7447..db3d2aae2 100644 --- a/auth/lib/ui/settings/data/data_section_widget.dart +++ b/auth/lib/ui/settings/data/data_section_widget.dart @@ -1,4 +1,3 @@ - import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/theme/ente_theme.dart'; import 'package:ente_auth/ui/components/captioned_text_widget.dart'; @@ -11,7 +10,9 @@ import 'package:ente_auth/utils/navigation_util.dart'; import 'package:flutter/material.dart'; class DataSectionWidget extends StatelessWidget { - DataSectionWidget({Key? key}) : super(key: key); + // final _logger = Logger("AccountSectionWidget"); + + DataSectionWidget({super.key}); @override Widget build(BuildContext context) { @@ -36,7 +37,7 @@ class DataSectionWidget extends StatelessWidget { trailingIcon: Icons.chevron_right_outlined, trailingIconIsMuted: true, onTap: () async { - routeToPage(context, ImportCodePage()); + routeToPage(context, const ImportCodePage()); }, ), sectionOptionSpacing, diff --git a/auth/lib/ui/settings/data/export_widget.dart b/auth/lib/ui/settings/data/export_widget.dart index cc9a1638b..e7d42cd98 100644 --- a/auth/lib/ui/settings/data/export_widget.dart +++ b/auth/lib/ui/settings/data/export_widget.dart @@ -9,14 +9,14 @@ import 'package:ente_auth/store/code_store.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; import 'package:share_plus/share_plus.dart'; @@ -76,17 +76,17 @@ Future _requestForEncryptionPassword( try { final kekSalt = CryptoUtil.getSaltToDeriveKey(); final derivedKeyResult = await CryptoUtil.deriveSensitiveKey( - utf8.encode(password) as Uint8List, + utf8.encode(password), kekSalt, ); String exportPlainText = await _getAuthDataForExport(); // Encrypt the key with this derived key - final encResult = await CryptoUtil.encryptChaCha( - utf8.encode(exportPlainText) as Uint8List, + final encResult = await CryptoUtil.encryptData( + utf8.encode(exportPlainText), derivedKeyResult.key, ); - final encContent = Sodium.bin2base64(encResult.encryptedData!); - final encNonce = Sodium.bin2base64(encResult.header!); + final encContent = CryptoUtil.bin2base64(encResult.encryptedData!); + final encNonce = CryptoUtil.bin2base64(encResult.header!); final EnteAuthExport data = EnteAuthExport( version: 1, encryptedData: encContent, @@ -94,11 +94,11 @@ Future _requestForEncryptionPassword( kdfParams: KDFParams( memLimit: derivedKeyResult.memLimit, opsLimit: derivedKeyResult.opsLimit, - salt: Sodium.bin2base64(kekSalt), + salt: CryptoUtil.bin2base64(kekSalt), ), ); // get json value of data - _exportCodes(context, jsonEncode(data.toJson())); + await _exportCodes(context, jsonEncode(data.toJson())); } catch (e, s) { Logger("ExportWidget").severe(e, s); showToast(context, "Error while exporting codes."); @@ -126,46 +126,55 @@ Future _showExportWarningDialog(BuildContext context) async { Future _exportCodes(BuildContext context, String fileContent) async { DateTime now = DateTime.now().toUtc(); String formattedDate = DateFormat('yyyy-MM-dd').format(now); - String exportFileName = 'ente-auth-codes-$formattedDate.txt'; - final _codeFile = File( - Configuration.instance.getTempDirectory() + exportFileName, - ); + String exportFileName = 'ente-auth-codes-$formattedDate'; + String exportFileExtension = 'txt'; final hasAuthenticated = await LocalAuthenticationService.instance .requestLocalAuthentication(context, context.l10n.authToExportCodes); + await PlatformUtil.refocusWindows(); if (!hasAuthenticated) { return; } - if (_codeFile.existsSync()) { - await _codeFile.delete(); - } - _codeFile.writeAsStringSync(fileContent); - final Size size = MediaQuery.of(context).size; - - if (Platform.isAndroid) { - await FileSaver.instance.saveAs( - name: exportFileName, - filePath: _codeFile.path, - mimeType: MimeType.text, - ext: 'txt', - ); - } else { - await Share.shareFiles( - [_codeFile.path], - sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2), - ); - } - Future.delayed(const Duration(seconds: 30), () async { - if (_codeFile.existsSync()) { - _codeFile.deleteSync(); - } - }); + Future.delayed( + const Duration(milliseconds: 1200), + () async => await shareDialog( + context, + context.l10n.exportCodes, + saveAction: () async { + await PlatformUtil.shareFile( + exportFileName, + exportFileExtension, + CryptoUtil.strToBin(fileContent), + MimeType.text, + ); + }, + sendAction: () async { + final codeFile = File( + "${Configuration.instance.getTempDirectory()}$exportFileName.$exportFileExtension", + ); + if (codeFile.existsSync()) { + await codeFile.delete(); + } + codeFile.writeAsStringSync(fileContent); + final Size size = MediaQuery.of(context).size; + await Share.shareFiles( + [codeFile.path], + sharePositionOrigin: Rect.fromLTWH(0, 0, size.width, size.height / 2), + ); + Future.delayed(const Duration(seconds: 30), () async { + if (codeFile.existsSync()) { + codeFile.deleteSync(); + } + }); + }, + ), + ); } Future _getAuthDataForExport() async { final codes = await CodeStore.instance.getAllCodes(); String data = ""; for (final code in codes) { - data += code.rawData + "\n"; + data += "${code.rawData}\n"; } return data; } diff --git a/auth/lib/ui/settings/data/import/aegis_import.dart b/auth/lib/ui/settings/data/import/aegis_import.dart index 5042d618f..b801e64a5 100644 --- a/auth/lib/ui/settings/data/import/aegis_import.dart +++ b/auth/lib/ui/settings/data/import/aegis_import.dart @@ -91,7 +91,7 @@ Future _processAegisExportFile( final jsonString = await file.readAsString(); final decodedJson = jsonDecode(jsonString); final isEncrypted = decodedJson['header']['slots'] != null; - var aegisDB; + Map? aegisDB; if (isEncrypted) { String? password; try { @@ -127,7 +127,7 @@ Future _processAegisExportFile( aegisDB = decodedJson['db']; } final parsedCodes = []; - for (var item in aegisDB['entries']) { + for (var item in aegisDB?['entries']) { var kind = item['type']; var account = item['name']; var issuer = item['issuer']; diff --git a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart index 15f869206..0cba96056 100644 --- a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart +++ b/auth/lib/ui/settings/data/import/encrypted_ente_import.dart @@ -11,14 +11,13 @@ import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/settings/data/import/import_success.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; import 'package:logging/logging.dart'; Future showEncryptedImportInstruction(BuildContext context) async { @@ -80,21 +79,21 @@ Future _decryptExportData( try { await progressDialog.show(); final derivedKey = await CryptoUtil.deriveKey( - utf8.encode(password) as Uint8List, - Sodium.base642bin(enteAuthExport.kdfParams.salt), + utf8.encode(password), + CryptoUtil.base642bin(enteAuthExport.kdfParams.salt), enteAuthExport.kdfParams.memLimit, enteAuthExport.kdfParams.opsLimit, ); Uint8List? decryptedContent; // Encrypt the key with this derived key try { - decryptedContent = await CryptoUtil.decryptChaCha( - Sodium.base642bin(enteAuthExport.encryptedData), + decryptedContent = await CryptoUtil.decryptData( + CryptoUtil.base642bin(enteAuthExport.encryptedData), derivedKey, - Sodium.base642bin(enteAuthExport.encryptionNonce), + CryptoUtil.base642bin(enteAuthExport.encryptionNonce), ); - } catch (e,s) { - Logger("encryptedImport").warning('failed to decrypt',e,s); + } catch (e, s) { + Logger("encryptedImport").warning('failed to decrypt', e, s); showToast(context, l10n.incorrectPasswordTitle); isPasswordIncorrect = true; } diff --git a/auth/lib/ui/settings/data/import/google_auth_import.dart b/auth/lib/ui/settings/data/import/google_auth_import.dart index d883f365c..60638f91c 100644 --- a/auth/lib/ui/settings/data/import/google_auth_import.dart +++ b/auth/lib/ui/settings/data/import/google_auth_import.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; import 'package:base32/base32.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/models/code.dart'; @@ -12,6 +11,8 @@ import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/scanner_gauth_page.dart'; import 'package:ente_auth/ui/settings/data/import/import_success.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; @@ -24,13 +25,14 @@ Future showGoogleAuthInstruction(BuildContext context) async { title: l10n.importFromApp("Google Authenticator"), body: l10n.importGoogleAuthGuide, buttons: [ - ButtonWidget( - buttonType: ButtonType.primary, - labelText: l10n.scanAQrCode, - isInAlert: true, - buttonSize: ButtonSize.large, - buttonAction: ButtonAction.first, - ), + if (PlatformUtil.isMobile()) + ButtonWidget( + buttonType: ButtonType.primary, + labelText: l10n.scanAQrCode, + isInAlert: true, + buttonSize: ButtonSize.large, + buttonAction: ButtonAction.first, + ), ButtonWidget( buttonType: ButtonType.secondary, labelText: context.l10n.cancel, diff --git a/auth/lib/ui/settings/data/import/import_service.dart b/auth/lib/ui/settings/data/import/import_service.dart index ab48924be..a510ec88b 100644 --- a/auth/lib/ui/settings/data/import/import_service.dart +++ b/auth/lib/ui/settings/data/import/import_service.dart @@ -1,4 +1,3 @@ -import 'package:ente_auth/ui/settings/data/import/2fas_import.dart'; import 'package:ente_auth/ui/settings/data/import/aegis_import.dart'; import 'package:ente_auth/ui/settings/data/import/bitwarden_import.dart'; import 'package:ente_auth/ui/settings/data/import/encrypted_ente_import.dart'; @@ -6,6 +5,7 @@ import 'package:ente_auth/ui/settings/data/import/google_auth_import.dart'; import 'package:ente_auth/ui/settings/data/import/lastpass_import.dart'; import 'package:ente_auth/ui/settings/data/import/plain_text_import.dart'; import 'package:ente_auth/ui/settings/data/import/raivo_plain_text_import.dart'; +import 'package:ente_auth/ui/settings/data/import/two_fas_import.dart'; import 'package:ente_auth/ui/settings/data/import_page.dart'; import 'package:flutter/cupertino.dart'; diff --git a/auth/lib/ui/settings/data/import/import_success.dart b/auth/lib/ui/settings/data/import/import_success.dart index cc77248e8..5827248f8 100644 --- a/auth/lib/ui/settings/data/import/import_success.dart +++ b/auth/lib/ui/settings/data/import/import_success.dart @@ -11,9 +11,9 @@ Future importSuccessDialog(BuildContext context, int count) async { firstButtonLabel: context.l10n.ok, firstButtonOnTap: () async { Navigator.of(context).pop(); - if(Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } + // if(Navigator.of(context).canPop()) { + // Navigator.of(context).pop(); + // } }, firstButtonType: ButtonType.primary, ); diff --git a/auth/lib/ui/settings/data/import/plain_text_import.dart b/auth/lib/ui/settings/data/import/plain_text_import.dart index a8e64bb5f..d76effd8c 100644 --- a/auth/lib/ui/settings/data/import/plain_text_import.dart +++ b/auth/lib/ui/settings/data/import/plain_text_import.dart @@ -48,10 +48,8 @@ class PlainTextImport extends StatelessWidget { ], ); } - } - Future showImportInstructionDialog(BuildContext context) async { final l10n = context.l10n; final AlertDialog alert = AlertDialog( @@ -94,7 +92,6 @@ Future showImportInstructionDialog(BuildContext context) async { ); } - Future _pickImportFile(BuildContext context) async { final l10n = context.l10n; FilePickerResult? result = await FilePicker.platform.pickFiles(); diff --git a/auth/lib/ui/settings/data/import/2fas_import.dart b/auth/lib/ui/settings/data/import/two_fas_import.dart similarity index 98% rename from auth/lib/ui/settings/data/import/2fas_import.dart rename to auth/lib/ui/settings/data/import/two_fas_import.dart index b6e75f3c6..ae5a05b0b 100644 --- a/auth/lib/ui/settings/data/import/2fas_import.dart +++ b/auth/lib/ui/settings/data/import/two_fas_import.dart @@ -170,8 +170,8 @@ Future _process2FasExportFile( } String decrypt2FasVault(dynamic data, {required String password}) { - int ITERATION_COUNT = 10000; - int KEY_SIZE = 256; + int iterationCount = 10000; + int keySize = 256; final String encryptedServices = data["servicesEncrypted"]; var split = encryptedServices.split(":"); final encryptedData = base64.decode(split[0]); @@ -181,11 +181,11 @@ String decrypt2FasVault(dynamic data, {required String password}) { final pbkdf2 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64)); final params = Pbkdf2Parameters( salt, - ITERATION_COUNT, - KEY_SIZE ~/ 8, + iterationCount, + keySize ~/ 8, ); pbkdf2.init(params); - Uint8List key = Uint8List(KEY_SIZE ~/ 8); + Uint8List key = Uint8List(keySize ~/ 8); pbkdf2.deriveKey(Uint8List.fromList(utf8.encode(password)), 0, key, 0); final decrypted = decrypt(key, iv, encryptedData); final utf8Decode = utf8.decode(decrypted); diff --git a/auth/lib/ui/settings/data/import_page.dart b/auth/lib/ui/settings/data/import_page.dart index 08a4d40d8..b7ac09846 100644 --- a/auth/lib/ui/settings/data/import_page.dart +++ b/auth/lib/ui/settings/data/import_page.dart @@ -21,7 +21,9 @@ enum ImportType { } class ImportCodePage extends StatelessWidget { - final List importOptions = [ + const ImportCodePage({super.key}); + + static const List importOptions = [ ImportType.plainText, ImportType.encrypted, ImportType.twoFas, @@ -32,8 +34,6 @@ class ImportCodePage extends StatelessWidget { ImportType.lastpass, ]; - ImportCodePage({super.key}); - String getTitle(BuildContext context, ImportType type) { switch (type) { case ImportType.plainText: diff --git a/auth/lib/ui/settings/debug_section_widget.dart b/auth/lib/ui/settings/debug_section_widget.dart index 259937ff7..03406f791 100644 --- a/auth/lib/ui/settings/debug_section_widget.dart +++ b/auth/lib/ui/settings/debug_section_widget.dart @@ -3,9 +3,9 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; import 'package:ente_auth/ui/settings/settings_section_title.dart'; import 'package:ente_auth/ui/settings/settings_text_item.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; class DebugSectionWidget extends StatelessWidget { const DebugSectionWidget({super.key}); @@ -51,7 +51,7 @@ class DebugSectionWidget extends StatelessWidget { "Key", style: TextStyle(fontWeight: FontWeight.bold), ), - Text(Sodium.bin2base64(Configuration.instance.getKey()!)), + Text(CryptoUtil.bin2base64(Configuration.instance.getKey()!)), const Padding(padding: EdgeInsets.all(12)), const Text( "Encrypted Key", diff --git a/auth/lib/ui/settings/faq.dart b/auth/lib/ui/settings/faq.dart index d9aa88f1b..5f441912e 100644 --- a/auth/lib/ui/settings/faq.dart +++ b/auth/lib/ui/settings/faq.dart @@ -8,8 +8,8 @@ import 'package:flutter/material.dart'; class FAQQuestionsWidget extends StatelessWidget { const FAQQuestionsWidget({ - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { @@ -64,9 +64,9 @@ class FAQQuestionsWidget extends StatelessWidget { class FaqWidget extends StatelessWidget { const FaqWidget({ - Key? key, + super.key, required this.faq, - }) : super(key: key); + }); final FaqItem? faq; diff --git a/auth/lib/ui/settings/general_section_widget.dart b/auth/lib/ui/settings/general_section_widget.dart index 5026166f6..7172da13c 100644 --- a/auth/lib/ui/settings/general_section_widget.dart +++ b/auth/lib/ui/settings/general_section_widget.dart @@ -17,7 +17,7 @@ import 'package:ente_auth/utils/toast_util.dart'; import 'package:flutter/material.dart'; class AdvancedSectionWidget extends StatefulWidget { - const AdvancedSectionWidget({Key? key}) : super(key: key); + const AdvancedSectionWidget({super.key}); @override State createState() => _AdvancedSectionWidgetState(); diff --git a/auth/lib/ui/settings/language_picker.dart b/auth/lib/ui/settings/language_picker.dart index 997e53068..3e97813fd 100644 --- a/auth/lib/ui/settings/language_picker.dart +++ b/auth/lib/ui/settings/language_picker.dart @@ -18,8 +18,8 @@ class LanguageSelectorPage extends StatelessWidget { this.supportedLocales, this.onLocaleChanged, this.currentLocale, { - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { @@ -79,8 +79,8 @@ class ItemsWidget extends StatefulWidget { this.supportedLocales, this.onLocaleChanged, this.currentLocale, { - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _ItemsWidgetState(); diff --git a/auth/lib/ui/settings/made_with_love_widget.dart b/auth/lib/ui/settings/made_with_love_widget.dart index ff1715efd..a8e12c106 100644 --- a/auth/lib/ui/settings/made_with_love_widget.dart +++ b/auth/lib/ui/settings/made_with_love_widget.dart @@ -4,8 +4,8 @@ import 'package:url_launcher/url_launcher.dart'; class MadeWithLoveWidget extends StatelessWidget { const MadeWithLoveWidget({ - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/security_section_widget.dart b/auth/lib/ui/settings/security_section_widget.dart index aa05dd62b..b3d3914c1 100644 --- a/auth/lib/ui/settings/security_section_widget.dart +++ b/auth/lib/ui/settings/security_section_widget.dart @@ -15,15 +15,15 @@ import 'package:ente_auth/ui/components/expandable_menu_item_widget.dart'; import 'package:ente_auth/ui/components/menu_item_widget.dart'; import 'package:ente_auth/ui/components/toggle_switch_widget.dart'; import 'package:ente_auth/ui/settings/common_settings.dart'; -import 'package:ente_auth/utils/crypto_util.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:ente_auth/utils/toast_util.dart'; +import 'package:ente_crypto_dart/ente_crypto_dart.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; class SecuritySectionWidget extends StatefulWidget { - const SecuritySectionWidget({Key? key}) : super(key: key); + const SecuritySectionWidget({super.key}); @override State createState() => _SecuritySectionWidgetState(); @@ -78,11 +78,12 @@ class _SecuritySectionWidgetState extends State { context, l10n.authToViewYourRecoveryKey, ); + await PlatformUtil.refocusWindows(); if (hasAuthenticated) { String recoveryKey; try { recoveryKey = - Sodium.bin2hex(Configuration.instance.getRecoveryKey()); + CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); } catch (e) { showGenericErrorDialog(context: context); return; @@ -113,6 +114,7 @@ class _SecuritySectionWidgetState extends State { ); final isEmailMFAEnabled = UserService.instance.hasEmailMFAEnabled(); + await PlatformUtil.refocusWindows(); if (hasAuthenticated) { await updateEmailMFA(!isEmailMFAEnabled); if (mounted) { @@ -136,6 +138,7 @@ class _SecuritySectionWidgetState extends State { context, context.l10n.authToViewYourActiveSessions, ); + await PlatformUtil.refocusWindows(); if (hasAuthenticated) { Navigator.of(context).push( MaterialPageRoute( @@ -167,6 +170,7 @@ class _SecuritySectionWidgetState extends State { context.l10n.lockScreenEnablePreSteps, ); if (hasAuthenticated) { + FocusScope.of(context).requestFocus(); setState(() {}); } }, diff --git a/auth/lib/ui/settings/settings_section_title.dart b/auth/lib/ui/settings/settings_section_title.dart index 08dbc69ec..8b7e549cf 100644 --- a/auth/lib/ui/settings/settings_section_title.dart +++ b/auth/lib/ui/settings/settings_section_title.dart @@ -8,9 +8,9 @@ class SettingsSectionTitle extends StatelessWidget { const SettingsSectionTitle( this.title, { - Key? key, + super.key, this.color, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/settings_text_item.dart b/auth/lib/ui/settings/settings_text_item.dart index 6273c7cad..afaf56432 100644 --- a/auth/lib/ui/settings/settings_text_item.dart +++ b/auth/lib/ui/settings/settings_text_item.dart @@ -8,10 +8,10 @@ class SettingsTextItem extends StatelessWidget { final String text; final IconData icon; const SettingsTextItem({ - Key? key, + super.key, required this.text, required this.icon, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/social_section_widget.dart b/auth/lib/ui/settings/social_section_widget.dart index 71016bf32..8e1655ac7 100644 --- a/auth/lib/ui/settings/social_section_widget.dart +++ b/auth/lib/ui/settings/social_section_widget.dart @@ -11,7 +11,7 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher_string.dart'; class SocialSectionWidget extends StatelessWidget { - const SocialSectionWidget({Key? key}) : super(key: key); + const SocialSectionWidget({super.key}); @override Widget build(BuildContext context) { @@ -67,9 +67,9 @@ class SocialsMenuItemWidget extends StatelessWidget { const SocialsMenuItemWidget( this.text, this.url, { - Key? key, + super.key, this.launchInExternalApp = true, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/support_dev_widget.dart b/auth/lib/ui/settings/support_dev_widget.dart index 63fa92c27..849b95415 100644 --- a/auth/lib/ui/settings/support_dev_widget.dart +++ b/auth/lib/ui/settings/support_dev_widget.dart @@ -10,8 +10,8 @@ import 'package:url_launcher/url_launcher.dart'; class SupportDevWidget extends StatelessWidget { const SupportDevWidget({ - Key? key, - }) : super(key: key); + super.key, + }); @override Widget build(BuildContext context) { diff --git a/auth/lib/ui/settings/support_section_widget.dart b/auth/lib/ui/settings/support_section_widget.dart index 8c3d1e8fe..77497be2a 100644 --- a/auth/lib/ui/settings/support_section_widget.dart +++ b/auth/lib/ui/settings/support_section_widget.dart @@ -12,7 +12,7 @@ import 'package:logging/logging.dart'; import 'package:url_launcher/url_launcher_string.dart'; class SupportSectionWidget extends StatefulWidget { - const SupportSectionWidget({Key? key}) : super(key: key); + const SupportSectionWidget({super.key}); @override State createState() => _SupportSectionWidgetState(); @@ -62,7 +62,7 @@ class _SupportSectionWidgetState extends State { trailingIconIsMuted: true, onTap: () async { launchUrlString( - githubDiscussionsUrl, + githubIssuesUrl, mode: LaunchMode.externalApplication, ); }, diff --git a/auth/lib/ui/settings/theme_switch_widget.dart b/auth/lib/ui/settings/theme_switch_widget.dart index 69e4513f1..c0495b65b 100644 --- a/auth/lib/ui/settings/theme_switch_widget.dart +++ b/auth/lib/ui/settings/theme_switch_widget.dart @@ -11,7 +11,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; class ThemeSwitchWidget extends StatefulWidget { - const ThemeSwitchWidget({Key? key}) : super(key: key); + const ThemeSwitchWidget({super.key}); @override State createState() => _ThemeSwitchWidgetState(); diff --git a/auth/lib/ui/settings/title_bar_widget.dart b/auth/lib/ui/settings/title_bar_widget.dart index e11a5c287..8d9aff85c 100644 --- a/auth/lib/ui/settings/title_bar_widget.dart +++ b/auth/lib/ui/settings/title_bar_widget.dart @@ -2,7 +2,12 @@ import 'package:ente_auth/l10n/l10n.dart'; import 'package:flutter/material.dart'; class SettingsTitleBarWidget extends StatelessWidget { - const SettingsTitleBarWidget({Key? key}) : super(key: key); + const SettingsTitleBarWidget({ + super.key, + required this.scaffoldKey, + }); + + final GlobalKey scaffoldKey; @override Widget build(BuildContext context) { @@ -17,7 +22,7 @@ class SettingsTitleBarWidget extends StatelessWidget { IconButton( visualDensity: const VisualDensity(horizontal: -2, vertical: -2), onPressed: () { - Navigator.pop(context); + scaffoldKey.currentState?.closeDrawer(); }, icon: const Icon(Icons.keyboard_double_arrow_left_outlined), ), diff --git a/auth/lib/ui/settings_page.dart b/auth/lib/ui/settings_page.dart index e5df8fcc3..5f516d7cc 100644 --- a/auth/lib/ui/settings_page.dart +++ b/auth/lib/ui/settings_page.dart @@ -25,18 +25,24 @@ import 'package:ente_auth/ui/settings/theme_switch_widget.dart'; import 'package:ente_auth/ui/settings/title_bar_widget.dart'; import 'package:ente_auth/utils/dialog_util.dart'; import 'package:ente_auth/utils/navigation_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; class SettingsPage extends StatelessWidget { final ValueNotifier emailNotifier; + final GlobalKey scaffoldKey; - SettingsPage({Key? key, required this.emailNotifier}) : super(key: key); + SettingsPage({ + super.key, + required this.emailNotifier, + required this.scaffoldKey, + }); @override Widget build(BuildContext context) { - final _hasLoggedIn = Configuration.instance.hasConfiguredAccount(); - if (_hasLoggedIn) { + final hasLoggedIn = Configuration.instance.hasConfiguredAccount(); + if (hasLoggedIn) { UserService.instance.getUserDetailsV2().ignore(); } final enteColorScheme = getEnteColorScheme(context); @@ -49,11 +55,11 @@ class SettingsPage extends StatelessWidget { } Widget _getBody(BuildContext context, EnteColorScheme colorScheme) { - final _hasLoggedIn = Configuration.instance.hasConfiguredAccount(); + final hasLoggedIn = Configuration.instance.hasConfiguredAccount(); final enteTextTheme = getEnteTextTheme(context); const sectionSpacing = SizedBox(height: 8); final List contents = []; - if (_hasLoggedIn) { + if (hasLoggedIn) { contents.add( Container( padding: const EdgeInsets.symmetric(horizontal: 8), @@ -110,6 +116,7 @@ class SettingsPage extends StatelessWidget { context, context.l10n.authToInitiateSignIn, ); + await PlatformUtil.refocusWindows(); if (!hasAuthenticated) { return; } @@ -161,7 +168,9 @@ class SettingsPage extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const SettingsTitleBarWidget(), + SettingsTitleBarWidget( + scaffoldKey: scaffoldKey, + ), Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 24), child: Column( diff --git a/auth/lib/ui/tools/app_lock.dart b/auth/lib/ui/tools/app_lock.dart index 2e7e0f54b..df55f8116 100644 --- a/auth/lib/ui/tools/app_lock.dart +++ b/auth/lib/ui/tools/app_lock.dart @@ -37,7 +37,7 @@ class AppLock extends StatefulWidget { final Locale locale; const AppLock({ - Key? key, + super.key, required this.builder, required this.lockScreen, required this.savedThemeMode, @@ -46,7 +46,7 @@ class AppLock extends StatefulWidget { this.backgroundLockLatency = const Duration(seconds: 0), this.darkTheme, this.lightTheme, - }) : super(key: key); + }); static _AppLockState? of(BuildContext context) => context.findAncestorStateOfType<_AppLockState>(); @@ -135,9 +135,9 @@ class _AppLockState extends State with WidgetsBindingObserver { } Widget get _lockScreen { - return WillPopScope( + return PopScope( child: this.widget.lockScreen, - onWillPop: () => Future.value(false), + canPop: false, ); } diff --git a/auth/lib/ui/tools/debug/log_file_viewer.dart b/auth/lib/ui/tools/debug/log_file_viewer.dart index cc9321aa4..2a85969ed 100644 --- a/auth/lib/ui/tools/debug/log_file_viewer.dart +++ b/auth/lib/ui/tools/debug/log_file_viewer.dart @@ -2,11 +2,12 @@ import 'dart:io'; import 'dart:ui'; import 'package:ente_auth/ui/common/loading_widget.dart'; +import 'package:ente_auth/utils/platform_util.dart'; import 'package:flutter/material.dart'; class LogFileViewer extends StatefulWidget { final File file; - const LogFileViewer(this.file, {Key? key}) : super(key: key); + const LogFileViewer(this.file, {super.key}); @override State createState() => _LogFileViewerState(); @@ -42,13 +43,17 @@ class _LogFileViewerState extends State { return Container( padding: const EdgeInsets.only(left: 12, top: 8, right: 12), child: SingleChildScrollView( - child: Text( - _logs!, - style: const TextStyle( - fontFeatures: [ - FontFeature.tabularFigures(), - ], - height: 1.2, + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: PlatformUtil.selectionControls, + child: Text( + _logs!, + style: const TextStyle( + fontFeatures: [ + FontFeature.tabularFigures(), + ], + height: 1.2, + ), ), ), ), diff --git a/auth/lib/ui/tools/lock_screen.dart b/auth/lib/ui/tools/lock_screen.dart index 213e7a99a..712aa233b 100644 --- a/auth/lib/ui/tools/lock_screen.dart +++ b/auth/lib/ui/tools/lock_screen.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; class LockScreen extends StatefulWidget { - const LockScreen({Key? key}) : super(key: key); + const LockScreen({super.key}); @override State createState() => _LockScreenState(); diff --git a/auth/lib/ui/two_factor_authentication_page.dart b/auth/lib/ui/two_factor_authentication_page.dart index 6287abdf8..a83a66baa 100644 --- a/auth/lib/ui/two_factor_authentication_page.dart +++ b/auth/lib/ui/two_factor_authentication_page.dart @@ -3,13 +3,13 @@ import 'package:ente_auth/services/user_service.dart'; import 'package:ente_auth/ui/lifecycle_event_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:pinput/pin_put/pin_put.dart'; + +import 'package:pinput/pinput.dart'; class TwoFactorAuthenticationPage extends StatefulWidget { final String sessionID; - const TwoFactorAuthenticationPage(this.sessionID, {Key? key}) - : super(key: key); + const TwoFactorAuthenticationPage(this.sessionID, {super.key}); @override State createState() => @@ -85,29 +85,31 @@ class _TwoFactorAuthenticationPageState const Padding(padding: EdgeInsets.all(32)), Padding( padding: const EdgeInsets.fromLTRB(40, 0, 40, 0), - child: PinPut( - fieldsCount: 6, - onSubmit: (String code) { + child: Pinput( + onSubmitted: (String code) { _verifyTwoFactorCode(code); }, + length: 6, + defaultPinTheme: const PinTheme(), + submittedPinTheme: PinTheme( + decoration: pinPutDecoration.copyWith( + borderRadius: BorderRadius.circular(20.0), + ), + ), + focusedPinTheme: PinTheme( + decoration: pinPutDecoration, + ), + followingPinTheme: PinTheme( + decoration: pinPutDecoration.copyWith( + borderRadius: BorderRadius.circular(5.0), + ), + ), onChanged: (String pin) { setState(() { _code = pin; }); }, controller: _pinController, - submittedFieldDecoration: pinPutDecoration.copyWith( - borderRadius: BorderRadius.circular(20.0), - ), - selectedFieldDecoration: pinPutDecoration, - followingFieldDecoration: pinPutDecoration.copyWith( - borderRadius: BorderRadius.circular(5.0), - ), - inputDecoration: const InputDecoration( - focusedBorder: InputBorder.none, - border: InputBorder.none, - counterText: '', - ), autofocus: true, ), ), diff --git a/auth/lib/ui/two_factor_recovery_page.dart b/auth/lib/ui/two_factor_recovery_page.dart index e007a9c40..c8a8b1aa6 100644 --- a/auth/lib/ui/two_factor_recovery_page.dart +++ b/auth/lib/ui/two_factor_recovery_page.dart @@ -14,8 +14,8 @@ class TwoFactorRecoveryPage extends StatefulWidget { this.sessionID, this.encryptedSecret, this.secretDecryptionNonce, { - Key? key, - }) : super(key: key); + super.key, + }); @override State createState() => _TwoFactorRecoveryPageState(); diff --git a/auth/lib/ui/utils/icon_utils.dart b/auth/lib/ui/utils/icon_utils.dart index 298a14333..771303952 100644 --- a/auth/lib/ui/utils/icon_utils.dart +++ b/auth/lib/ui/utils/icon_utils.dart @@ -87,7 +87,7 @@ class IconUtils { Color? _getAdaptiveColor(String? hexColor, BuildContext context) { if (hexColor == null) return null; final theme = Theme.of(context).brightness; - final color = Color(int.parse("0xFF" + hexColor)); + final color = Color(int.parse("0xFF$hexColor")); // Color is close to neutral-grey and it's too light or dark for theme if (_isCloseToNeutralGrey(color) && ((theme == Brightness.light && _getColorLuminance(color) > 0.70) || diff --git a/auth/lib/utils/auth_util.dart b/auth/lib/utils/auth_util.dart index 0a6320191..b543b4c73 100644 --- a/auth/lib/utils/auth_util.dart +++ b/auth/lib/utils/auth_util.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:ente_auth/l10n/l10n.dart'; import 'package:flutter/cupertino.dart'; +import 'package:flutter_local_authentication/flutter_local_authentication.dart'; import 'package:local_auth/local_auth.dart'; import 'package:local_auth_android/local_auth_android.dart'; import 'package:local_auth_ios/types/auth_messages_ios.dart'; @@ -7,32 +10,36 @@ import 'package:logging/logging.dart'; Future requestAuthentication(BuildContext context, String reason) async { Logger("AuthUtil").info("Requesting authentication"); - await LocalAuthentication().stopAuthentication(); - final l10n = context.l10n; - return await LocalAuthentication().authenticate( - localizedReason: reason, - authMessages: [ - AndroidAuthMessages( - biometricHint: l10n.androidBiometricHint, - biometricNotRecognized: l10n.androidBiometricNotRecognized, - biometricRequiredTitle: l10n.androidBiometricRequiredTitle, - biometricSuccess: l10n.androidBiometricSuccess, - cancelButton: l10n.androidCancelButton, - deviceCredentialsRequiredTitle: - l10n.androidDeviceCredentialsRequiredTitle, - deviceCredentialsSetupDescription: - l10n.androidDeviceCredentialsSetupDescription, - goToSettingsButton: l10n.goToSettings, - goToSettingsDescription: l10n.androidGoToSettingsDescription, - signInTitle: l10n.androidSignInTitle, - ), - IOSAuthMessages( - goToSettingsButton: l10n.goToSettings, - goToSettingsDescription: l10n.goToSettings, - lockOut: l10n.iOSLockOut, - // cancelButton default value is "Ok" - cancelButton: l10n.iOSOkButton, - ), - ], - ); + if (Platform.isMacOS || Platform.isLinux) { + return await FlutterLocalAuthentication().authenticate(); + } else { + await LocalAuthentication().stopAuthentication(); + final l10n = context.l10n; + return await LocalAuthentication().authenticate( + localizedReason: reason, + authMessages: [ + AndroidAuthMessages( + biometricHint: l10n.androidBiometricHint, + biometricNotRecognized: l10n.androidBiometricNotRecognized, + biometricRequiredTitle: l10n.androidBiometricRequiredTitle, + biometricSuccess: l10n.androidBiometricSuccess, + cancelButton: l10n.androidCancelButton, + deviceCredentialsRequiredTitle: + l10n.androidDeviceCredentialsRequiredTitle, + deviceCredentialsSetupDescription: + l10n.androidDeviceCredentialsSetupDescription, + goToSettingsButton: l10n.goToSettings, + goToSettingsDescription: l10n.androidGoToSettingsDescription, + signInTitle: l10n.androidSignInTitle, + ), + IOSAuthMessages( + goToSettingsButton: l10n.goToSettings, + goToSettingsDescription: l10n.goToSettings, + lockOut: l10n.iOSLockOut, + // cancelButton default value is "Ok" + cancelButton: l10n.iOSOkButton, + ), + ], + ); + } } diff --git a/auth/lib/utils/crypto_util.dart b/auth/lib/utils/crypto_util.dart deleted file mode 100644 index 494ef130f..000000000 --- a/auth/lib/utils/crypto_util.dart +++ /dev/null @@ -1,509 +0,0 @@ -import 'dart:convert'; -import 'dart:io' as io; -import 'dart:typed_data'; - -import 'package:computer/computer.dart'; -import 'package:ente_auth/core/errors.dart'; -import 'package:ente_auth/models/derived_key_result.dart'; -import 'package:ente_auth/models/encryption_result.dart'; -import 'package:ente_auth/utils/device_info.dart'; -import 'package:flutter_sodium/flutter_sodium.dart'; -import 'package:logging/logging.dart'; - -const int encryptionChunkSize = 4 * 1024 * 1024; -final int decryptionChunkSize = - encryptionChunkSize + Sodium.cryptoSecretstreamXchacha20poly1305Abytes; -const int hashChunkSize = 4 * 1024 * 1024; -const int loginSubKeyLen = 32; -const int loginSubKeyId = 1; -const String loginSubKeyContext = "loginctx"; - -Uint8List cryptoSecretboxEasy(Map args) { - return Sodium.cryptoSecretboxEasy(args["source"], args["nonce"], args["key"]); -} - -Uint8List cryptoSecretboxOpenEasy(Map args) { - return Sodium.cryptoSecretboxOpenEasy( - args["cipher"], - args["nonce"], - args["key"], - ); -} - -Uint8List cryptoPwHash(Map args) { - return Sodium.cryptoPwhash( - Sodium.cryptoSecretboxKeybytes, - args["password"], - args["salt"], - args["opsLimit"], - args["memLimit"], - Sodium.cryptoPwhashAlgArgon2id13, - ); -} - -Uint8List cryptoKdfDeriveFromKey( - Map args, -) { - return Sodium.cryptoKdfDeriveFromKey( - args["subkeyLen"], - args["subkeyId"], - args["context"], - args["key"], - ); -} - -// Returns the hash for a given file, chunking it in batches of hashChunkSize -Future cryptoGenericHash(Map args) async { - final sourceFile = io.File(args["sourceFilePath"]); - final sourceFileLength = await sourceFile.length(); - final inputFile = sourceFile.openSync(mode: io.FileMode.read); - final state = - Sodium.cryptoGenerichashInit(null, Sodium.cryptoGenerichashBytesMax); - var bytesRead = 0; - bool isDone = false; - while (!isDone) { - var chunkSize = hashChunkSize; - if (bytesRead + chunkSize >= sourceFileLength) { - chunkSize = sourceFileLength - bytesRead; - isDone = true; - } - final buffer = await inputFile.read(chunkSize); - bytesRead += chunkSize; - Sodium.cryptoGenerichashUpdate(state, buffer); - } - await inputFile.close(); - return Sodium.cryptoGenerichashFinal(state, Sodium.cryptoGenerichashBytesMax); -} - -EncryptionResult chachaEncryptData(Map args) { - final initPushResult = - Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]); - final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push( - initPushResult.state, - args["source"], - null, - Sodium.cryptoSecretstreamXchacha20poly1305TagFinal, - ); - return EncryptionResult( - encryptedData: encryptedData, - header: initPushResult.header, - ); -} - -// Encrypts a given file, in chunks of encryptionChunkSize -Future chachaEncryptFile(Map args) async { - final encryptionStartTime = DateTime.now().millisecondsSinceEpoch; - final logger = Logger("ChaChaEncrypt"); - final sourceFile = io.File(args["sourceFilePath"]); - final destinationFile = io.File(args["destinationFilePath"]); - final sourceFileLength = await sourceFile.length(); - logger.info("Encrypting file of size " + sourceFileLength.toString()); - - final inputFile = sourceFile.openSync(mode: io.FileMode.read); - final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen(); - final initPushResult = - Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key); - var bytesRead = 0; - var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage; - while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) { - var chunkSize = encryptionChunkSize; - if (bytesRead + chunkSize >= sourceFileLength) { - chunkSize = sourceFileLength - bytesRead; - tag = Sodium.cryptoSecretstreamXchacha20poly1305TagFinal; - } - final buffer = await inputFile.read(chunkSize); - bytesRead += chunkSize; - final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push( - initPushResult.state, - buffer, - null, - tag, - ); - await destinationFile.writeAsBytes(encryptedData, mode: io.FileMode.append); - } - await inputFile.close(); - - logger.info( - "Encryption time: " + - (DateTime.now().millisecondsSinceEpoch - encryptionStartTime) - .toString(), - ); - - return EncryptionResult(key: key, header: initPushResult.header); -} - -Future chachaDecryptFile(Map args) async { - final logger = Logger("ChaChaDecrypt"); - final decryptionStartTime = DateTime.now().millisecondsSinceEpoch; - final sourceFile = io.File(args["sourceFilePath"]); - final destinationFile = io.File(args["destinationFilePath"]); - final sourceFileLength = await sourceFile.length(); - logger.info("Decrypting file of size " + sourceFileLength.toString()); - - final inputFile = sourceFile.openSync(mode: io.FileMode.read); - final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull( - args["header"], - args["key"], - ); - - var bytesRead = 0; - var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage; - while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) { - var chunkSize = decryptionChunkSize; - if (bytesRead + chunkSize >= sourceFileLength) { - chunkSize = sourceFileLength - bytesRead; - } - final buffer = await inputFile.read(chunkSize); - bytesRead += chunkSize; - final pullResult = - Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null); - await destinationFile.writeAsBytes(pullResult.m, mode: io.FileMode.append); - tag = pullResult.tag; - } - inputFile.closeSync(); - - logger.info( - "ChaCha20 Decryption time: " + - (DateTime.now().millisecondsSinceEpoch - decryptionStartTime) - .toString(), - ); -} - -Uint8List chachaDecryptData(Map args) { - final pullState = Sodium.cryptoSecretstreamXchacha20poly1305InitPull( - args["header"], - args["key"], - ); - final pullResult = Sodium.cryptoSecretstreamXchacha20poly1305Pull( - pullState, - args["source"], - null, - ); - return pullResult.m; -} - -class CryptoUtil { - // Note: workers are turned on during app startup. - static final Computer _computer = Computer.shared(); - - static init() { - Sodium.init(); - } - - static Uint8List base642bin( - String b64, { - String? ignore, - int variant = Sodium.base64VariantOriginal, - }) { - return Sodium.base642bin(b64, ignore: ignore, variant: variant); - } - - static String bin2base64( - Uint8List bin, { - bool urlSafe = false, - }) { - return Sodium.bin2base64( - bin, - variant: - urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal, - ); - } - - static String bin2hex(Uint8List bin) { - return Sodium.bin2hex(bin); - } - - static Uint8List hex2bin(String hex) { - return Sodium.hex2bin(hex); - } - - // Encrypts the given source, with the given key and a randomly generated - // nonce, using XSalsa20 (w Poly1305 MAC). - // This function runs on the same thread as the caller, so should be used only - // for small amounts of data where thread switching can result in a degraded - // user experience - static EncryptionResult encryptSync(Uint8List source, Uint8List key) { - final nonce = Sodium.randombytesBuf(Sodium.cryptoSecretboxNoncebytes); - - final args = {}; - args["source"] = source; - args["nonce"] = nonce; - args["key"] = key; - final encryptedData = cryptoSecretboxEasy(args); - return EncryptionResult( - key: key, - nonce: nonce, - encryptedData: encryptedData, - ); - } - - // Decrypts the given cipher, with the given key and nonce using XSalsa20 - // (w Poly1305 MAC). - static Future decrypt( - Uint8List cipher, - Uint8List key, - Uint8List nonce, - ) async { - final args = {}; - args["cipher"] = cipher; - args["nonce"] = nonce; - args["key"] = key; - return _computer.compute( - cryptoSecretboxOpenEasy, - param: args, - taskName: "decrypt", - ); - } - - // Decrypts the given cipher, with the given key and nonce using XSalsa20 - // (w Poly1305 MAC). - // This function runs on the same thread as the caller, so should be used only - // for small amounts of data where thread switching can result in a degraded - // user experience - static Uint8List decryptSync( - Uint8List cipher, - Uint8List key, - Uint8List nonce, - ) { - final args = {}; - args["cipher"] = cipher; - args["nonce"] = nonce; - args["key"] = key; - return cryptoSecretboxOpenEasy(args); - } - - // Encrypts the given source, with the given key and a randomly generated - // nonce, using XChaCha20 (w Poly1305 MAC). - // This function runs on the isolate pool held by `_computer`. - // TODO: Remove "ChaCha", an implementation detail from the function name - static Future encryptChaCha( - Uint8List source, - Uint8List key, - ) async { - final args = {}; - args["source"] = source; - args["key"] = key; - return _computer.compute( - chachaEncryptData, - param: args, - taskName: "encryptChaCha", - ); - } - - // Decrypts the given source, with the given key and header using XChaCha20 - // (w Poly1305 MAC). - // TODO: Remove "ChaCha", an implementation detail from the function name - static Future decryptChaCha( - Uint8List source, - Uint8List key, - Uint8List header, - ) async { - final args = {}; - args["source"] = source; - args["key"] = key; - args["header"] = header; - return _computer.compute( - chachaDecryptData, - param: args, - taskName: "decryptChaCha", - ); - } - - // Encrypts the file at sourceFilePath, with the key (if provided) and a - // randomly generated nonce using XChaCha20 (w Poly1305 MAC), and writes it - // to the destinationFilePath. - // If a key is not provided, one is generated and returned. - static Future encryptFile( - String sourceFilePath, - String destinationFilePath, { - Uint8List? key, - }) { - final args = {}; - args["sourceFilePath"] = sourceFilePath; - args["destinationFilePath"] = destinationFilePath; - args["key"] = key; - return _computer.compute( - chachaEncryptFile, - param: args, - taskName: "encryptFile", - ); - } - - // Decrypts the file at sourceFilePath, with the given key and header using - // XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath. - static Future decryptFile( - String sourceFilePath, - String destinationFilePath, - Uint8List header, - Uint8List key, - ) { - final args = {}; - args["sourceFilePath"] = sourceFilePath; - args["destinationFilePath"] = destinationFilePath; - args["header"] = header; - args["key"] = key; - return _computer.compute( - chachaDecryptFile, - param: args, - taskName: "decryptFile", - ); - } - - // Generates and returns a 256-bit key. - static Uint8List generateKey() { - return Sodium.cryptoSecretboxKeygen(); - } - - // Generates and returns a random byte buffer of length - // crypto_pwhash_SALTBYTES (16) - static Uint8List getSaltToDeriveKey() { - return Sodium.randombytesBuf(Sodium.cryptoPwhashSaltbytes); - } - - // Generates and returns a secret key and the corresponding public key. - static Future generateKeyPair() async { - return Sodium.cryptoBoxKeypair(); - } - - // Decrypts the input using the given publicKey-secretKey pair - static Uint8List openSealSync( - Uint8List input, - Uint8List publicKey, - Uint8List secretKey, - ) { - return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey); - } - - // Encrypts the input using the given publicKey - static Uint8List sealSync(Uint8List input, Uint8List publicKey) { - return Sodium.cryptoBoxSeal(input, publicKey); - } - - // Derives a key for a given password and salt using Argon2id, v1.3. - // The function first attempts to derive a key with both memLimit and opsLimit - // set to their Sensitive variants. - // If this fails, say on a device with insufficient RAM, we retry by halving - // the memLimit and doubling the opsLimit, while ensuring that we stay within - // the min and max limits for both parameters. - // At all points, we ensure that the product of these two variables (the area - // under the graph that determines the amount of work required) is a constant. - static Future deriveSensitiveKey( - Uint8List password, - Uint8List salt, - ) async { - final logger = Logger("pwhash"); - int memLimit = Sodium.cryptoPwhashMemlimitSensitive; - int opsLimit = Sodium.cryptoPwhashOpslimitSensitive; - if (await isLowSpecDevice()) { - logger.info("low spec device detected"); - // When sensitive memLimit (1 GB) is used, on low spec device the OS might - // kill the app with OOM. To avoid that, start with 256 MB and - // corresponding ops limit (16). - // This ensures that the product of these two variables - // (the area under the graph that determines the amount of work required) - // stays the same - // SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE: 1073741824 - // SODIUM_CRYPTO_PWHASH_MEMLIMIT_MODERATE: 268435456 - // SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE: 4 - memLimit = Sodium.cryptoPwhashMemlimitModerate; - final factor = Sodium.cryptoPwhashMemlimitSensitive ~/ - Sodium.cryptoPwhashMemlimitModerate; // = 4 - opsLimit = opsLimit * factor; // = 16 - } - Uint8List key; - while (memLimit >= Sodium.cryptoPwhashMemlimitMin && - opsLimit <= Sodium.cryptoPwhashOpslimitMax) { - try { - key = await deriveKey(password, salt, memLimit, opsLimit); - return DerivedKeyResult(key, memLimit, opsLimit); - } catch (e, s) { - logger.warning( - "failed to deriveKey mem: $memLimit, ops: $opsLimit", - e, - s, - ); - } - memLimit = (memLimit / 2).round(); - opsLimit = opsLimit * 2; - } - throw UnsupportedError("Cannot perform this operation on this device"); - } - - // Derives a key for the given password and salt, using Argon2id, v1.3 - // with memory and ops limit hardcoded to their Interactive variants - // NOTE: This is only used while setting passwords for shared links, as an - // extra layer of authentication (atop the access token and collection key). - // More details @ https://ente.io/blog/building-shareable-links/ - static Future deriveInteractiveKey( - Uint8List password, - Uint8List salt, - ) async { - final int memLimit = Sodium.cryptoPwhashMemlimitInteractive; - final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive; - final key = await deriveKey(password, salt, memLimit, opsLimit); - return DerivedKeyResult(key, memLimit, opsLimit); - } - - // Derives a key for a given password, salt, memLimit and opsLimit using - // Argon2id, v1.3. - static Future deriveKey( - Uint8List password, - Uint8List salt, - int memLimit, - int opsLimit, - ) async { - try { - return await _computer.compute( - cryptoPwHash, - param: { - "password": password, - "salt": salt, - "memLimit": memLimit, - "opsLimit": opsLimit, - }, - taskName: "deriveKey", - ); - } catch (e, s) { - final String errMessage = 'failed to deriveKey memLimit: $memLimit and ' - 'opsLimit: $opsLimit'; - Logger("CryptoUtilDeriveKey").warning(errMessage, e, s); - throw KeyDerivationError(); - } - } - - // derives a Login key as subKey from the given key by applying KDF - // (Key Derivation Function) with the `loginSubKeyId` and - // `loginSubKeyLen` and `loginSubKeyContext` as context - static Future deriveLoginKey( - Uint8List key, - ) async { - try { - final Uint8List derivedKey = await _computer.compute( - cryptoKdfDeriveFromKey, - param: { - "key": key, - "subkeyId": loginSubKeyId, - "subkeyLen": loginSubKeyLen, - "context": utf8.encode(loginSubKeyContext), - }, - taskName: "deriveLoginKey", - ); - // return the first 16 bytes of the derived key - return derivedKey.sublist(0, 16); - } catch (e, s) { - Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s); - throw LoginKeyDerivationError(); - } - } - - // Computes and returns the hash of the source file - static Future getHash(io.File source) { - return _computer.compute( - cryptoGenericHash, - param: { - "sourceFilePath": source.path, - }, - taskName: "fileHash", - ); - } -} diff --git a/auth/lib/utils/data_util.dart b/auth/lib/utils/data_util.dart deleted file mode 100644 index 3dcae58fa..000000000 --- a/auth/lib/utils/data_util.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'dart:math'; - -double convertBytesToGBs(final int bytes, {int precision = 2}) { - return double.parse( - (bytes / (1024 * 1024 * 1024)).toStringAsFixed(precision), - ); -} - -final storageUnits = ["bytes", "KB", "MB", "GB"]; - -String convertBytesToReadableFormat(int bytes) { - int storageUnitIndex = 0; - while (bytes >= 1024 && storageUnitIndex < storageUnits.length - 1) { - storageUnitIndex++; - bytes = (bytes / 1024).round(); - } - return bytes.toString() + " " + storageUnits[storageUnitIndex]; -} - -String formatBytes(int bytes, [int decimals = 2]) { - if (bytes == 0) return '0 bytes'; - const k = 1024; - final int dm = decimals < 0 ? 0 : decimals; - final int i = (log(bytes) / log(k)).floor(); - return ((bytes / pow(k, i)).toStringAsFixed(dm)) + ' ' + storageUnits[i]; -} diff --git a/auth/lib/utils/device_info.dart b/auth/lib/utils/device_info.dart deleted file mode 100644 index 5832ec6de..000000000 --- a/auth/lib/utils/device_info.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:io'; - -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:flutter/foundation.dart'; -import 'package:logging/logging.dart'; - -late DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); - -// https://gist.github.com/adamawolf/3048717 -late Set iOSLowEndMachineCodes = { - "iPhone5,1", //iPhone 5 (GSM) - "iPhone5,2", //iPhone 5 (GSM+CDMA) - "iPhone5,3", //iPhone 5C (GSM) - "iPhone5,4", //iPhone 5C (Global) - "iPhone6,1", //iPhone 5S (GSM) - "iPhone6,2", //iPhone 5S (Global) - "iPhone7,1", //iPhone 6 Plus - "iPhone7,2", //iPhone 6 - "iPhone8,1", // iPhone 6s - "iPhone8,2", // iPhone 6s Plus - "iPhone8,4", // iPhone SE (GSM) - "iPhone9,1", // iPhone 7 - "iPhone9,2", // iPhone 7 Plus - "iPhone9,3", // iPhone 7 - "iPhone9,4", // iPhone 7 Plus - "iPhone10,1", // iPhone 8 - "iPhone10,2", // iPhone 8 Plus - "iPhone10,3", // iPhone X Global - "iPhone10,4", // iPhone 8 - "iPhone10,5", // iPhone 8 -}; - -Future isLowSpecDevice() async { - try { - if (Platform.isIOS) { - final IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo; - debugPrint("ios utc name ${iosInfo.utsname.machine}"); - return iOSLowEndMachineCodes.contains(iosInfo.utsname.machine); - } - } catch (e) { - Logger("device_info").severe("deviceSpec check failed", e); - } - return false; -} diff --git a/auth/lib/utils/dialog_util.dart b/auth/lib/utils/dialog_util.dart index 9cf7273fd..d24608b78 100644 --- a/auth/lib/utils/dialog_util.dart +++ b/auth/lib/utils/dialog_util.dart @@ -47,11 +47,11 @@ Future showErrorDialogForException({ String apiErrorPrefix = "It looks like something went wrong.", }) async { String errorMessage = context.l10n.tempErrorContactSupportIfPersists; - if (exception is DioError && + if (exception is DioException && exception.response != null && exception.response!.data["code"] != null) { errorMessage = - "$apiErrorPrefix\n\nReason: " + exception.response!.data["code"]; + "$apiErrorPrefix\n\nReason: ${exception.response!.data["code"]}"; } return showDialogWidget( context: context, diff --git a/auth/lib/utils/directory_utils.dart b/auth/lib/utils/directory_utils.dart new file mode 100644 index 000000000..b9da6e613 --- /dev/null +++ b/auth/lib/utils/directory_utils.dart @@ -0,0 +1,12 @@ +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +class DirectoryUtils { + static Future getDatabasePath(String databaseName) async => p.joinAll( + [ + (await getApplicationDocumentsDirectory()).path, + "ente", + ".$databaseName", + ], + ); +} diff --git a/auth/lib/utils/email_util.dart b/auth/lib/utils/email_util.dart index 0da675f21..dce1c7aef 100644 --- a/auth/lib/utils/email_util.dart +++ b/auth/lib/utils/email_util.dart @@ -4,22 +4,21 @@ import 'package:archive/archive_io.dart'; import 'package:email_validator/email_validator.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/logging/super_logging.dart'; -import 'package:ente_auth/ente_theme_data.dart'; import 'package:ente_auth/l10n/l10n.dart'; import 'package:ente_auth/ui/components/buttons/button_widget.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart'; -// import 'package:ente_auth/ui/tools/debug/log_file_viewer.dart'; import 'package:ente_auth/utils/dialog_util.dart'; +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:ente_auth/utils/share_utils.dart'; import 'package:ente_auth/utils/toast_util.dart'; -import 'package:file_saver/file_saver.dart'; +import "package:file_saver/file_saver.dart"; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_email_sender/flutter_email_sender.dart'; -import 'package:intl/intl.dart'; +import "package:intl/intl.dart"; import 'package:logging/logging.dart'; -// import 'package:open_mail_app/open_mail_app.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; @@ -27,7 +26,10 @@ import 'package:url_launcher/url_launcher.dart'; final Logger _logger = Logger('email_util'); -bool isValidEmail(String email) { +bool isValidEmail(String? email) { + if (email == null) { + return false; + } return EmailValidator.validate(email); } @@ -40,90 +42,73 @@ Future sendLogs( String? body, }) async { final l10n = context.l10n; - final List actions = [ - TextButton( - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Icon( - Icons.feed_outlined, - color: Theme.of(context).iconTheme.color?.withOpacity(0.85), - ), - const Padding(padding: EdgeInsets.all(4)), - Text( - l10n.viewLogsAction, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .defaultTextColor - .withOpacity(0.85), + showDialogWidget( + context: context, + title: title, + icon: Icons.bug_report_outlined, + body: l10n.sendLogsDescription, + buttons: [ + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: l10n.reportABug, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: false, + onTap: () async { + await _sendLogs(context, toEmail, subject, body); + if (postShare != null) { + postShare(); + } + }, + ), + //isInAlert is false here as we don't want to the dialog to dismiss + //on pressing this button + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: l10n.viewLogsAction, + buttonAction: ButtonAction.second, + onTap: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return LogFileViewer(SuperLogging.logFile!); + }, + barrierColor: Colors.black87, + barrierDismissible: false, + ); + }, + ), + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.exportLogs, + buttonAction: ButtonAction.third, + onTap: () async { + Future.delayed( + const Duration(milliseconds: 200), + () => shareDialog( + context, + title, + saveAction: () async { + final zipFilePath = await getZippedLogsFile(context); + await exportLogs(context, zipFilePath); + }, + sendAction: () async { + final zipFilePath = await getZippedLogsFile(context); + await exportLogs(context, zipFilePath, true); + }, ), - ), - ], + ); + }, ), - onPressed: () async { - showDialog( - context: context, - builder: (BuildContext context) { - return LogFileViewer(SuperLogging.logFile!); - }, - barrierColor: Colors.black87, - barrierDismissible: false, - ); - }, - ), - TextButton( - child: Text( - title, - style: TextStyle( - color: Theme.of(context).colorScheme.alternativeColor, - ), - ), - onPressed: () async { - Navigator.of(context, rootNavigator: true).pop('dialog'); - await _sendLogs(context, toEmail, subject, body); - if (postShare != null) { - postShare(); - } - }, - ), - ]; - final List content = []; - content.addAll( - [ - Text( - l10n.sendLogsDescription, - style: const TextStyle( - height: 1.5, - fontSize: 16, - ), - ), - const Padding(padding: EdgeInsets.all(12)), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: actions, + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.cancel, + buttonAction: ButtonAction.cancel, ), ], ); - final confirmation = AlertDialog( - title: Text( - title, - style: const TextStyle( - fontSize: 18, - ), - ), - content: SingleChildScrollView( - child: ListBody( - children: content, - ), - ), - ); - showDialog( - context: context, - builder: (_) { - return confirmation; - }, - ); } Future _sendLogs( @@ -144,6 +129,7 @@ Future _sendLogs( await FlutterEmailSender.send(email); } catch (e, s) { _logger.severe('email sender failed', e, s); + Navigator.of(context, rootNavigator: true).pop(); await shareLogs(context, toEmail, zipFilePath); } } @@ -153,13 +139,13 @@ Future getZippedLogsFile(BuildContext context) async { final dialog = createProgressDialog(context, l10n.preparingLogsTitle); await dialog.show(); final logsPath = (await getApplicationSupportDirectory()).path; - final logsDirectory = Directory(logsPath + "/logs"); + final logsDirectory = Directory("$logsPath/logs"); final tempPath = (await getTemporaryDirectory()).path; final zipFilePath = - tempPath + "/logs-${Configuration.instance.getUserID() ?? 0}.zip"; + "$tempPath/logs-${Configuration.instance.getUserID() ?? 0}.zip"; final encoder = ZipFileEncoder(); encoder.create(zipFilePath); - encoder.addDirectory(logsDirectory); + await encoder.addDirectory(logsDirectory); encoder.close(); await dialog.hide(); return zipFilePath; @@ -170,14 +156,15 @@ Future shareLogs( String toEmail, String zipFilePath, ) async { + final l10n = context.l10n; final result = await showDialogWidget( context: context, - title: context.l10n.emailYourLogs, - body: context.l10n.pleaseSendTheLogsTo(toEmail), + title: l10n.emailYourLogs, + body: l10n.pleaseSendTheLogsTo(toEmail), buttons: [ ButtonWidget( buttonType: ButtonType.neutral, - labelText: context.l10n.copyEmailAddress, + labelText: l10n.copyEmailAddress, isInAlert: true, buttonAction: ButtonAction.first, onTap: () async { @@ -187,13 +174,13 @@ Future shareLogs( ), ButtonWidget( buttonType: ButtonType.neutral, - labelText: context.l10n.exportLogs, + labelText: l10n.exportLogs, isInAlert: true, buttonAction: ButtonAction.second, ), ButtonWidget( buttonType: ButtonType.secondary, - labelText: context.l10n.cancel, + labelText: l10n.cancel, isInAlert: true, buttonAction: ButtonAction.cancel, ), @@ -204,18 +191,24 @@ Future shareLogs( } } -Future exportLogs(BuildContext context, String zipFilePath) async { +Future exportLogs( + BuildContext context, + String zipFilePath, [ + bool isSharing = false, +]) async { final Size size = MediaQuery.of(context).size; - if (Platform.isAndroid) { - DateTime now = DateTime.now().toUtc(); - String shortMonthName = DateFormat('MMM').format(now); // Short month name - String logFileName = + if (!isSharing) { + final DateTime now = DateTime.now().toUtc(); + final String shortMonthName = DateFormat('MMM').format(now); // Short month + final String logFileName = 'ente-logs-${now.year}-$shortMonthName-${now.day}-${now.hour}-${now.minute}'; - await FileSaver.instance.saveAs( - name: logFileName, - filePath: zipFilePath, - mimeType: MimeType.zip, - ext: 'zip', + + final bytes = await File(zipFilePath).readAsBytes(); + await PlatformUtil.shareFile( + logFileName, + 'zip', + bytes, + MimeType.zip, ); } else { await Share.shareXFiles( @@ -233,8 +226,8 @@ Future sendEmail( }) async { try { final String clientDebugInfo = await _clientInfo(); - final String _subject = subject ?? '[Support]'; - final String _body = (body ?? '') + clientDebugInfo; + final String subject0 = subject ?? '[Support]'; + final String body0 = (body ?? '') + clientDebugInfo; // final EmailContent email = EmailContent( // to: [ // to, @@ -248,7 +241,7 @@ Future sendEmail( final Uri params = Uri( scheme: 'mailto', path: to, - query: 'subject=$_subject&body=$_body', + query: 'subject=$subject0&body=$body0', ); if (await canLaunchUrl(params)) { await launchUrl(params); @@ -278,27 +271,15 @@ Future _clientInfo() async { void _showNoMailAppsDialog(BuildContext context, String toEmail) { final l10n = context.l10n; - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(l10n.emailUsMessage(toEmail)), - actions: [ - TextButton( - child: Text(l10n.copyEmailAction), - onPressed: () async { - await Clipboard.setData(ClipboardData(text: toEmail)); - showShortToast(context, l10n.copied); - }, - ), - TextButton( - child: Text(l10n.ok), - onPressed: () { - Navigator.pop(context); - }, - ), - ], - ); + showChoiceDialog( + context, + icon: Icons.email_outlined, + title: l10n.emailUsMessage(toEmail), + firstButtonLabel: l10n.copyEmailAddress, + secondButtonLabel: l10n.ok, + firstButtonOnTap: () async { + await Clipboard.setData(ClipboardData(text: toEmail)); + showShortToast(context, l10n.copied); }, ); } diff --git a/auth/lib/utils/navigation_util.dart b/auth/lib/utils/navigation_util.dart index 38a3dbe53..58aa0fb85 100644 --- a/auth/lib/utils/navigation_util.dart +++ b/auth/lib/utils/navigation_util.dart @@ -109,9 +109,9 @@ class SwipeableRouteBuilder extends PageRoute { class TransparentRoute extends PageRoute { TransparentRoute({ required this.builder, - RouteSettings? settings, + super.settings, }) : assert(builder != null), - super(settings: settings, fullscreenDialog: false); + super(fullscreenDialog: false); final WidgetBuilder? builder; diff --git a/auth/lib/utils/package_info_util.dart b/auth/lib/utils/package_info_util.dart new file mode 100644 index 000000000..0bb5c2458 --- /dev/null +++ b/auth/lib/utils/package_info_util.dart @@ -0,0 +1,20 @@ +import 'package:ente_auth/utils/platform_util.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class PackageInfoUtil { + Future getPackageInfo() async { + return await PackageInfo.fromPlatform(); + } + + String getVersion(PackageInfo info) { + return info.version; + } + + String getPackageName(PackageInfo info) { + if (PlatformUtil.isMobile()) { + return info.packageName; + } else { + return 'io.ente.auth'; + } + } +} diff --git a/auth/lib/utils/platform_util.dart b/auth/lib/utils/platform_util.dart new file mode 100644 index 000000000..dc0245cb0 --- /dev/null +++ b/auth/lib/utils/platform_util.dart @@ -0,0 +1,93 @@ +import 'dart:io'; + +import 'package:desktop_webview_window/desktop_webview_window.dart'; +import 'package:ente_auth/ui/common/web_page.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:window_manager/window_manager.dart'; + +class PlatformUtil { + static bool isDesktop() { + return !kIsWeb && + (Platform.isWindows || Platform.isLinux || Platform.isMacOS); + } + + static bool isMobile() { + return !kIsWeb && (Platform.isAndroid || Platform.isIOS); + } + + static bool isWeb() { + return kIsWeb; + } + + static TextSelectionControls get selectionControls => Platform.isAndroid + ? materialTextSelectionControls + : Platform.isIOS + ? cupertinoTextSelectionControls + : desktopTextSelectionControls; + + static openWebView(BuildContext context, String title, String url) async { + if (PlatformUtil.isDesktop()) { + if (!await WebviewWindow.isWebviewAvailable()) { + launchUrlString(url); + return; + } + + final webview = await WebviewWindow.create( + configuration: CreateConfiguration( + title: title, + ), + ); + webview.launch(url); + return; + } + Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) { + return WebPage( + title, + url, + ); + }, + ), + ); + } + + static Future shareFile( + String fileName, + String extension, + Uint8List bytes, + MimeType type, + ) async { + try { + if (Platform.isAndroid || Platform.isIOS) { + await FileSaver.instance.saveAs( + name: fileName, + ext: extension, + bytes: bytes, + mimeType: type, + ); + } else { + await FileSaver.instance.saveFile( + name: fileName, + ext: extension, + bytes: bytes, + mimeType: type, + ); + } + } catch (e) {} + } + + // Needed to fix issue with local_auth on Windows + // https://github.com/flutter/flutter/issues/122322 + static Future refocusWindows() async { + if (!Platform.isWindows) return; + await windowManager.blur(); + await windowManager.focus(); + await windowManager.setAlwaysOnTop(true); + await windowManager.setAlwaysOnTop(false); + } +} diff --git a/auth/lib/utils/share_utils.dart b/auth/lib/utils/share_utils.dart new file mode 100644 index 000000000..32e779ea3 --- /dev/null +++ b/auth/lib/utils/share_utils.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:ente_auth/l10n/l10n.dart'; +import 'package:ente_auth/ui/components/buttons/button_widget.dart'; +import 'package:ente_auth/ui/components/dialog_widget.dart'; +import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:flutter/material.dart'; + +Future shareDialog( + BuildContext context, + String title, { + required Function saveAction, + required Function sendAction, +}) async { + final l10n = context.l10n; + showDialogWidget( + context: context, + title: title, + body: Platform.isLinux || Platform.isWindows + ? l10n.saveOnlyDescription + : l10n.saveOrSendDescription, + buttons: [ + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.neutral, + labelText: l10n.save, + buttonAction: ButtonAction.first, + shouldSurfaceExecutionStates: false, + onTap: () async { + await saveAction(); + }, + ), + if (!Platform.isWindows && !Platform.isLinux) + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.send, + buttonAction: ButtonAction.second, + onTap: () async { + await sendAction(); + }, + ), + ButtonWidget( + isInAlert: true, + buttonType: ButtonType.secondary, + labelText: l10n.cancel, + buttonAction: ButtonAction.cancel, + ), + ], + ); +} diff --git a/auth/lib/utils/toast_util.dart b/auth/lib/utils/toast_util.dart index a9f0392c2..319c0eb91 100644 --- a/auth/lib/utils/toast_util.dart +++ b/auth/lib/utils/toast_util.dart @@ -1,5 +1,6 @@ import 'package:ente_auth/ente_theme_data.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:fluttertoast/fluttertoast.dart'; Future showToast( @@ -8,16 +9,42 @@ Future showToast( toastLength = Toast.LENGTH_LONG, iOSDismissOnTap = true, }) async { - await Fluttertoast.cancel(); - return Fluttertoast.showToast( - msg: message, - toastLength: toastLength, - gravity: ToastGravity.BOTTOM, - timeInSecForIosWeb: 1, - backgroundColor: Theme.of(context).colorScheme.toastBackgroundColor, - textColor: Theme.of(context).colorScheme.toastTextColor, - fontSize: 16.0, - ); + try { + await Fluttertoast.cancel(); + return Fluttertoast.showToast( + msg: message, + toastLength: toastLength, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 1, + backgroundColor: Theme.of(context).colorScheme.toastBackgroundColor, + textColor: Theme.of(context).colorScheme.toastTextColor, + fontSize: 16.0, + ); + } on MissingPluginException catch (_) { + Widget toast = Container( + padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(25.0), + color: Theme.of(context).colorScheme.toastBackgroundColor, + ), + child: Text( + message, + style: TextStyle( + color: Theme.of(context).colorScheme.toastTextColor, + fontSize: 16.0, + ), + ), + ); + + final fToast = FToast(); + fToast.init(context); + + fToast.showToast( + child: toast, + gravity: ToastGravity.BOTTOM, + toastDuration: const Duration(seconds: 2), + ); + } } Future showShortToast(context, String message) { diff --git a/auth/lib/utils/window_protocol_handler.dart b/auth/lib/utils/window_protocol_handler.dart new file mode 100644 index 000000000..c0886f72d --- /dev/null +++ b/auth/lib/utils/window_protocol_handler.dart @@ -0,0 +1,54 @@ +import 'dart:io'; + +import 'package:ffi/ffi.dart'; +import 'package:win32/win32.dart'; + +const _hive = HKEY_CURRENT_USER; + +class WindowsProtocolHandler { + void register(String scheme, {String? executable, List? arguments}) { + final prefix = _regPrefix(scheme); + final capitalized = scheme[0].toUpperCase() + scheme.substring(1); + final cmd = executable ?? Platform.resolvedExecutable; + + _regCreateStringKey(_hive, prefix, '', 'URL:$capitalized'); + _regCreateStringKey(_hive, prefix, 'URL Protocol', ''); + _regCreateStringKey(_hive, '$prefix\\shell\\open\\command', '', cmd); + } + + void unregister(String scheme) { + final txtKey = TEXT(_regPrefix(scheme)); + try { + RegDeleteTree(HKEY_CURRENT_USER, txtKey); + } finally { + free(txtKey); + } + } + + String _regPrefix(String scheme) => 'SOFTWARE\\Classes\\$scheme'; + + int _regCreateStringKey(int hKey, String key, String valueName, String data) { + final txtKey = TEXT(key); + final txtValue = TEXT(valueName); + final txtData = TEXT(data); + try { + return RegSetKeyValue( + hKey, + txtKey, + txtValue, + REG_SZ, + txtData, + txtData.length * 2 + 2, + ); + } finally { + free(txtKey); + free(txtValue); + free(txtData); + } + } + + String _sanitize(String value) { + value = value.replaceAll(r'%s', '%1').replaceAll(r'"', '\\"'); + return '"$value"'; + } +} diff --git a/auth/linux/CMakeLists.txt b/auth/linux/CMakeLists.txt index 68e736ee3..ca3bd0a60 100644 --- a/auth/linux/CMakeLists.txt +++ b/auth/linux/CMakeLists.txt @@ -4,7 +4,7 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "authenticator") +set(BINARY_NAME "ente_auth") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID set(APPLICATION_ID "io.ente.auth") diff --git a/auth/linux/flutter/generated_plugin_registrant.cc b/auth/linux/flutter/generated_plugin_registrant.cc index 53de79447..0ed133fb1 100644 --- a/auth/linux/flutter/generated_plugin_registrant.cc +++ b/auth/linux/flutter/generated_plugin_registrant.cc @@ -6,22 +6,54 @@ #include "generated_plugin_registrant.h" +#include #include +#include #include +#include +#include #include +#include +#include +#include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_webview_window_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopWebviewWindowPlugin"); + desktop_webview_window_plugin_register_with_registrar(desktop_webview_window_registrar); g_autoptr(FlPluginRegistrar) file_saver_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); file_saver_plugin_register_with_registrar(file_saver_registrar); + g_autoptr(FlPluginRegistrar) flutter_local_authentication_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalAuthenticationPlugin"); + flutter_local_authentication_plugin_register_with_registrar(flutter_local_authentication_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); + g_autoptr(FlPluginRegistrar) screen_retriever_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); + screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); g_autoptr(FlPluginRegistrar) sentry_flutter_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "SentryFlutterPlugin"); sentry_flutter_plugin_register_with_registrar(sentry_flutter_registrar); + g_autoptr(FlPluginRegistrar) smart_auth_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SmartAuthPlugin"); + smart_auth_plugin_register_with_registrar(smart_auth_registrar); + g_autoptr(FlPluginRegistrar) sodium_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "SodiumLibsPlugin"); + sodium_libs_plugin_register_with_registrar(sodium_libs_registrar); + g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin"); + sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) window_manager_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); + window_manager_plugin_register_with_registrar(window_manager_registrar); } diff --git a/auth/linux/flutter/generated_plugins.cmake b/auth/linux/flutter/generated_plugins.cmake index 4b981d07a..71d04ad88 100644 --- a/auth/linux/flutter/generated_plugins.cmake +++ b/auth/linux/flutter/generated_plugins.cmake @@ -3,10 +3,18 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_webview_window file_saver + flutter_local_authentication flutter_secure_storage_linux + gtk + screen_retriever sentry_flutter + smart_auth + sodium_libs + sqlite3_flutter_libs url_launcher_linux + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/auth/linux/my_application.cc b/auth/linux/my_application.cc index 95b3a369c..bf557c528 100644 --- a/auth/linux/my_application.cc +++ b/auth/linux/my_application.cc @@ -7,17 +7,27 @@ #include "flutter/generated_plugin_registrant.h" -struct _MyApplication { +struct _MyApplication +{ GtkApplication parent_instance; - char** dart_entrypoint_arguments; + char **dart_entrypoint_arguments; }; G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Implements GApplication::activate. -static void my_application_activate(GApplication* application) { - MyApplication* self = MY_APPLICATION(application); - GtkWindow* window = +static void my_application_activate(GApplication *application) +{ + MyApplication *self = MY_APPLICATION(application); + + GList *windows = gtk_application_get_windows(GTK_APPLICATION(application)); + if (windows) + { + gtk_window_present(GTK_WINDOW(windows->data)); + return; + } + + GtkWindow *window = GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); // Use a header bar when running in GNOME as this is the common style used @@ -29,31 +39,36 @@ static void my_application_activate(GApplication* application) { // if future cases occur). gboolean use_header_bar = TRUE; #ifdef GDK_WINDOWING_X11 - GdkScreen* screen = gtk_window_get_screen(window); - if (GDK_IS_X11_SCREEN(screen)) { - const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); - if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + GdkScreen *screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) + { + const gchar *wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) + { use_header_bar = FALSE; } } #endif - if (use_header_bar) { - GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + if (use_header_bar) + { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "authenticator"); + gtk_header_bar_set_title(header_bar, "ente Auth"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); - } else { - gtk_window_set_title(window, "authenticator"); + } + else + { + gtk_window_set_title(window, "ente Auth"); } gtk_window_set_default_size(window, 1280, 720); - gtk_widget_show(GTK_WIDGET(window)); + gtk_widget_realize(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); - FlView* view = fl_view_new(project); + FlView *view = fl_view_new(project); gtk_widget_show(GTK_WIDGET(view)); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); @@ -63,42 +78,47 @@ static void my_application_activate(GApplication* application) { } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { - MyApplication* self = MY_APPLICATION(application); +static gboolean my_application_local_command_line(GApplication *application, gchar ***arguments, int *exit_status) +{ + MyApplication *self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; - if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; + if (!g_application_register(application, nullptr, &error)) + { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); *exit_status = 0; - return TRUE; + return FALSE; } // Implements GObject::dispose. -static void my_application_dispose(GObject* object) { - MyApplication* self = MY_APPLICATION(object); +static void my_application_dispose(GObject *object) +{ + MyApplication *self = MY_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); G_OBJECT_CLASS(my_application_parent_class)->dispose(object); } -static void my_application_class_init(MyApplicationClass* klass) { +static void my_application_class_init(MyApplicationClass *klass) +{ G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } -static void my_application_init(MyApplication* self) {} +static void my_application_init(MyApplication *self) {} -MyApplication* my_application_new() { +MyApplication *my_application_new() +{ return MY_APPLICATION(g_object_new(my_application_get_type(), "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, + "flags", G_APPLICATION_HANDLES_COMMAND_LINE | G_APPLICATION_HANDLES_OPEN, nullptr)); } diff --git a/auth/linux/packaging/appimage/make_config.yaml b/auth/linux/packaging/appimage/make_config.yaml new file mode 100644 index 000000000..782ce13a1 --- /dev/null +++ b/auth/linux/packaging/appimage/make_config.yaml @@ -0,0 +1,26 @@ +display_name: Auth +license: GPLv3 + +icon: assets/icon-light.png + +keywords: + - Authentication + - 2FA + +generic_name: Ente Authentication + +categories: + - Utility + +startup_notify: false + +# You can specify the shared libraries that you want to bundle with your app +# +# flutter_distributor automatically detects the shared libraries that your app +# depends on, but you can also specify them manually here. +# +# The following example shows how to bundle the libcurl library with your app. +# +# include: +# - libcurl.so.4 +include: [] diff --git a/auth/linux/packaging/deb/make_config.yaml b/auth/linux/packaging/deb/make_config.yaml new file mode 100644 index 000000000..65ae3bd65 --- /dev/null +++ b/auth/linux/packaging/deb/make_config.yaml @@ -0,0 +1,31 @@ +display_name: Auth +package_name: auth +maintainer: + name: Ente.io Developers + email: human@ente.io +priority: optional +section: x11 +essential: false +license: GPLv3 +icon: assets/icon-light.png +installed_size: 36000 + +dependencies: + - libwebkit2gtk-4.0-37 + - libsqlite3-0 + - libsodium23 + - libsecret-1-0 + +keywords: + - Authentication + - 2FA + +generic_name: Ente Authentication + +categories: + - Utility + +startup_notify: false + +supported_mime_type: + - x-scheme-handler/ente diff --git a/auth/linux/packaging/rpm/make_config.yaml b/auth/linux/packaging/rpm/make_config.yaml new file mode 100644 index 000000000..09e25d1a0 --- /dev/null +++ b/auth/linux/packaging/rpm/make_config.yaml @@ -0,0 +1,30 @@ +icon: assets/icon-light.png +summary: 2FA app with free end-to-end encrypted backup and sync +group: Application/Utility +vendor: Ente.io +packager: Ente.io Developers +packagerEmail: human@ente.io +license: GPLv3 +url: https://github.com/ente-io/auth + +display_name: Auth + +dependencies: + - libsqlite3x + - webkit2gtk-4.0 + - libsodium + - libsecret + +keywords: + - Authentication + - 2FA + +generic_name: Ente Authentication + +categories: + - Utility + +startup_notify: false + +supported_mime_type: + - x-scheme-handler/ente diff --git a/auth/macos/Flutter/GeneratedPluginRegistrant.swift b/auth/macos/Flutter/GeneratedPluginRegistrant.swift index 6b18cc381..58190b0b5 100644 --- a/auth/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/auth/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,30 +5,48 @@ import FlutterMacOS import Foundation -import connectivity_macos +import app_links +import connectivity_plus +import desktop_webview_window import device_info_plus import file_saver +import flutter_inappwebview_macos +import flutter_local_authentication import flutter_local_notifications import flutter_secure_storage_macos import package_info_plus import path_provider_foundation +import screen_retriever import sentry_flutter import share_plus import shared_preferences_foundation +import smart_auth +import sodium_libs import sqflite +import sqlite3_flutter_libs import url_launcher_macos +import window_manager func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + DesktopWebviewWindowPlugin.register(with: registry.registrar(forPlugin: "DesktopWebviewWindowPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FlutterLocalAuthenticationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalAuthenticationPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SentryFlutterPlugin.register(with: registry.registrar(forPlugin: "SentryFlutterPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SmartAuthPlugin.register(with: registry.registrar(forPlugin: "SmartAuthPlugin")) + SodiumLibsPlugin.register(with: registry.registrar(forPlugin: "SodiumLibsPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/auth/macos/Podfile b/auth/macos/Podfile index 049abe295..0c308d4f5 100644 --- a/auth/macos/Podfile +++ b/auth/macos/Podfile @@ -36,5 +36,8 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) - end + target.build_configurations.each do |build_configuration| + build_configuration.build_settings['export_xcargs'] = '-allowProvisioningUpdates' + end +end end diff --git a/auth/macos/Podfile.lock b/auth/macos/Podfile.lock index 678bce8da..8ac74ca4a 100644 --- a/auth/macos/Podfile.lock +++ b/auth/macos/Podfile.lock @@ -1,102 +1,180 @@ PODS: - - connectivity_macos (0.0.1): + - app_links (1.0.0): + - FlutterMacOS + - connectivity_plus (0.0.1): + - FlutterMacOS + - ReachabilitySwift + - desktop_webview_window (0.0.1): - FlutterMacOS - - Reachability - device_info_plus (0.0.1): - FlutterMacOS + - file_saver (0.0.1): + - FlutterMacOS + - flutter_inappwebview_macos (0.0.1): + - FlutterMacOS + - OrderedSet (~> 5.0) + - flutter_local_authentication (1.2.0): + - FlutterMacOS - flutter_local_notifications (0.0.1): - FlutterMacOS - flutter_secure_storage_macos (6.1.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - FMDB (2.7.5): - - FMDB/standard (= 2.7.5) - - FMDB/standard (2.7.5) - - package_info_plus_macos (0.0.1): + - OrderedSet (5.0.0) + - package_info_plus (0.0.1): - FlutterMacOS - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - Reachability (3.2) - - Sentry/HybridSDK (7.31.5) + - ReachabilitySwift (5.0.0) + - screen_retriever (0.0.1): + - FlutterMacOS + - Sentry/HybridSDK (8.19.0): + - SentryPrivate (= 8.19.0) - sentry_flutter (0.0.1): - Flutter - FlutterMacOS - - Sentry/HybridSDK (= 7.31.5) - - share_plus_macos (0.0.1): + - Sentry/HybridSDK (= 8.19.0) + - SentryPrivate (8.19.0) + - share_plus (0.0.1): - FlutterMacOS - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.2): + - smart_auth (0.0.1): - FlutterMacOS - - FMDB (>= 2.7.5) + - sodium_libs (2.2.0): + - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - 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): + - FlutterMacOS + - sqlite3 (~> 3.45.1) + - sqlite3/fts5 + - sqlite3/perf-threadsafe + - sqlite3/rtree - url_launcher_macos (0.0.1): - FlutterMacOS + - window_manager (0.2.0): + - FlutterMacOS DEPENDENCIES: - - connectivity_macos (from `Flutter/ephemeral/.symlinks/plugins/connectivity_macos/macos`) + - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) + - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) + - desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_saver (from `Flutter/ephemeral/.symlinks/plugins/file_saver/macos`) + - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) + - flutter_local_authentication (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos`) - flutter_local_notifications (from `Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - - package_info_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos`) - - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos`) + - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) - - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) + - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) + - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) + - smart_auth (from `Flutter/ephemeral/.symlinks/plugins/smart_auth/macos`) + - sodium_libs (from `Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos`) + - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) + - sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) SPEC REPOS: trunk: - - FMDB - - Reachability + - OrderedSet + - ReachabilitySwift - Sentry + - SentryPrivate + - sqlite3 EXTERNAL SOURCES: - connectivity_macos: - :path: Flutter/ephemeral/.symlinks/plugins/connectivity_macos/macos + app_links: + :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos + connectivity_plus: + :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos + desktop_webview_window: + :path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_saver: + :path: Flutter/ephemeral/.symlinks/plugins/file_saver/macos + flutter_inappwebview_macos: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos + flutter_local_authentication: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_authentication/macos flutter_local_notifications: :path: Flutter/ephemeral/.symlinks/plugins/flutter_local_notifications/macos flutter_secure_storage_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos FlutterMacOS: :path: Flutter/ephemeral - package_info_plus_macos: - :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus_macos/macos + package_info_plus: + :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + screen_retriever: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos sentry_flutter: :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos - share_plus_macos: - :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos + share_plus: + :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: - :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/macos + :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin + smart_auth: + :path: Flutter/ephemeral/.symlinks/plugins/smart_auth/macos + sodium_libs: + :path: Flutter/ephemeral/.symlinks/plugins/sodium_libs/macos sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos + :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin + sqlite3_flutter_libs: + :path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + window_manager: + :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - connectivity_macos: 5dae6ee11d320fac7c05f0d08bd08fc32b5514d9 + app_links: 4481ed4d71f384b0c3ae5016f4633aa73d32ff67 + connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 + desktop_webview_window: d4365e71bcd4e1aa0c14cf0377aa24db0c16a7e2 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f + file_saver: 44e6fbf666677faf097302460e214e977fdd977b + flutter_inappwebview_macos: 9600c9df9fdb346aaa8933812009f8d94304203d + flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0 flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4 flutter_secure_storage_macos: d56e2d218c1130b262bef8b4a7d64f88d7f9c9ea FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - package_info_plus_macos: f010621b07802a241d96d01876d6705f15e77c1c - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - Sentry: 4c9babff9034785067c896fd580b1f7de44da020 - sentry_flutter: 1346a880b24c0240807b53b10cf50ddad40f504e - share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea + OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c + package_info_plus: 02d7a575e80f194102bef286361c6c326e4c29ce + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 + Sentry: 1ebcaef678a27c8ac515f974cb5425dd1bbdec2f + sentry_flutter: ecdfbedee55337205561cfa782ee02d31ec83e1f + SentryPrivate: 765c9b4ebe9ac1a5fcdc067c5a1cfbf3f10e1677 + share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + smart_auth: b38e3ab4bfe089eacb1e233aca1a2340f96c28e9 + sodium_libs: 72d908a356801223ec77444e67844b3fe87d996a + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec + sqlite3: 73b7fc691fdc43277614250e04d183740cb15078 + sqlite3_flutter_libs: 06a05802529659a272beac4ee1350bfec294f386 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 + window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 -PODFILE CHECKSUM: 353c8bcc5d5b0994e508d035b5431cfe18c1dea7 +PODFILE CHECKSUM: f401c31c8f7c5571f6f565c78915d54338812dab -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.3 diff --git a/auth/macos/Runner.xcodeproj/project.pbxproj b/auth/macos/Runner.xcodeproj/project.pbxproj index 6d9ed401f..051c533ff 100644 --- a/auth/macos/Runner.xcodeproj/project.pbxproj +++ b/auth/macos/Runner.xcodeproj/project.pbxproj @@ -55,7 +55,7 @@ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* authenticator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = authenticator.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* ente Auth.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ente Auth.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -94,7 +94,6 @@ 4F2F733D93DB4D2D82767271 /* Pods-Runner.release.xcconfig */, B347CC163E4E13C897729F91 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -123,7 +122,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* authenticator.app */, + 33CC10ED2044A3C60003C045 /* ente Auth.app */, ); name = Products; sourceTree = ""; @@ -193,7 +192,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* authenticator.app */; + productReference = 33CC10ED2044A3C60003C045 /* ente Auth.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -203,13 +202,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -420,13 +418,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.auth.mac; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -546,13 +549,18 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.auth.mac; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -566,13 +574,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_IDENTITY = "-"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 10.14; + OTHER_CODE_SIGN_FLAGS = "--timestamp"; + PRODUCT_BUNDLE_IDENTIFIER = io.ente.auth.mac; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3fc3ba1d4..f8c5c553b 100644 --- a/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 000000000..5ce618cd8 Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png new file mode 100644 index 000000000..d81a7f741 Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png new file mode 100644 index 000000000..762eaf21b Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png new file mode 100644 index 000000000..8b988a10d Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png new file mode 100644 index 000000000..25e0b33bb Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png new file mode 100644 index 000000000..f0ec91f47 Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png new file mode 100644 index 000000000..cfd3497a9 Binary files /dev/null and b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64.png differ diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index a2ec33f19..2003d5bb1 100644 --- a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1 @@ -{ - "images" : [ - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_16.png", - "scale" : "1x" - }, - { - "size" : "16x16", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "2x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_32.png", - "scale" : "1x" - }, - { - "size" : "32x32", - "idiom" : "mac", - "filename" : "app_icon_64.png", - "scale" : "2x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_128.png", - "scale" : "1x" - }, - { - "size" : "128x128", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "2x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_256.png", - "scale" : "1x" - }, - { - "size" : "256x256", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "2x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_512.png", - "scale" : "1x" - }, - { - "size" : "512x512", - "idiom" : "mac", - "filename" : "app_icon_1024.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} +{"images":[{"size":"128x128","expected-size":"128","filename":"128.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"256x256","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"128x128","expected-size":"256","filename":"256.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"256x256","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"512x512","expected-size":"512","filename":"512.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"16","filename":"16.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"1x"},{"size":"16x16","expected-size":"32","filename":"32.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"32x32","expected-size":"64","filename":"64.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"},{"size":"512x512","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"mac","scale":"2x"}]} \ No newline at end of file diff --git a/auth/macos/Runner/Configs/AppInfo.xcconfig b/auth/macos/Runner/Configs/AppInfo.xcconfig index 7c1cda27a..b716deabf 100644 --- a/auth/macos/Runner/Configs/AppInfo.xcconfig +++ b/auth/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,12 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = auth +PRODUCT_NAME = ente Auth // The application's bundle identifier PRODUCT_BUNDLE_IDENTIFIER = io.ente.auth // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 io.ente.auth. All rights reserved. + +DEVELOPMENT_TEAM = 6Z68YJY9Q2 \ No newline at end of file diff --git a/auth/macos/Runner/DebugProfile.entitlements b/auth/macos/Runner/DebugProfile.entitlements index c946719a1..bc070d381 100644 --- a/auth/macos/Runner/DebugProfile.entitlements +++ b/auth/macos/Runner/DebugProfile.entitlements @@ -10,5 +10,9 @@ com.apple.security.network.client + keychain-access-groups + + com.apple.security.files.downloads.read-write + diff --git a/auth/macos/Runner/Info.plist b/auth/macos/Runner/Info.plist index 4789daa6a..418d695c9 100644 --- a/auth/macos/Runner/Info.plist +++ b/auth/macos/Runner/Info.plist @@ -28,5 +28,20 @@ MainMenu NSPrincipalClass NSApplication + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + otpauth + + + + LSApplicationCategoryType + public.app-category.security diff --git a/auth/macos/Runner/MainFlutterWindow.swift b/auth/macos/Runner/MainFlutterWindow.swift index 2722837ec..a823d046a 100644 --- a/auth/macos/Runner/MainFlutterWindow.swift +++ b/auth/macos/Runner/MainFlutterWindow.swift @@ -1,5 +1,6 @@ import Cocoa import FlutterMacOS +import window_manager class MainFlutterWindow: NSWindow { override func awakeFromNib() { @@ -12,4 +13,9 @@ class MainFlutterWindow: NSWindow { super.awakeFromNib() } + + override public func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) { + super.order(place, relativeTo: otherWin) + hiddenWindowAtLaunch() + } } diff --git a/auth/macos/Runner/Release.entitlements b/auth/macos/Runner/Release.entitlements index 48271acc9..e04b5e06c 100644 --- a/auth/macos/Runner/Release.entitlements +++ b/auth/macos/Runner/Release.entitlements @@ -4,7 +4,11 @@ com.apple.security.app-sandbox + com.apple.security.files.downloads.read-write + com.apple.security.network.client - + + keychain-access-groups + diff --git a/auth/macos/build/.last_build_id b/auth/macos/build/.last_build_id new file mode 100644 index 000000000..637ab0382 --- /dev/null +++ b/auth/macos/build/.last_build_id @@ -0,0 +1 @@ +f51f5c3bcecb0339dc02189e9dd2c2c8 \ No newline at end of file diff --git a/auth/macos/packaging/dmg/make_config.yaml b/auth/macos/packaging/dmg/make_config.yaml new file mode 100644 index 000000000..785f3d8ed --- /dev/null +++ b/auth/macos/packaging/dmg/make_config.yaml @@ -0,0 +1,11 @@ +title: Auth +icon: ../../../assets/icon-light.png +contents: + - x: 448 + y: 344 + type: link + path: "/Applications" + - x: 192 + y: 344 + type: file + path: ente Auth.app diff --git a/auth/pubspec.lock b/auth/pubspec.lock index 10c1a4093..f14cea91e 100644 --- a/auth/pubspec.lock +++ b/auth/pubspec.lock @@ -5,34 +5,50 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "64.0.0" adaptive_theme: dependency: "direct main" description: name: adaptive_theme - sha256: "3568bb526d4823c7bb35f9ce3604af15e04cc0e9cc4f257da3604fe6b48d74ae" + sha256: f4ee609b464e5efc68131d9d15ba9aa1de4e3b5ede64be17781c6e19a52d637d url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.6.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.2.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links: + dependency: "direct main" + description: + name: app_links + sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb" + url: "https://pub.dev" + source: hosted + version: "3.5.0" archive: dependency: "direct main" description: name: archive - sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" url: "https://pub.dev" source: hosted - version: "3.3.7" + version: "3.4.10" args: dependency: transitive description: @@ -73,14 +89,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" - bloc_test: - dependency: "direct dev" - description: - name: bloc_test - sha256: "43d5b2f3d09ba768d6b611151bdf20ca141ffb46e795eb9550a58c9c2f4eae3f" - url: "https://pub.dev" - source: hosted - version: "9.1.3" boolean_selector: dependency: transitive description: @@ -93,10 +101,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -109,34 +117,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.8" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.7+1" + version: "7.3.0" built_collection: dependency: transitive description: @@ -149,10 +157,10 @@ packages: dependency: transitive description: name: built_value - sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" + sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6 url: "https://pub.dev" source: hosted - version: "8.6.1" + version: "8.9.0" characters: dependency: transitive description: @@ -169,6 +177,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" clipboard: dependency: "direct main" description: @@ -189,27 +205,18 @@ packages: dependency: transitive description: name: code_builder - sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.10.0" collection: dependency: "direct main" description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" - computer: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "82e365fed8a1a76f6eea0220de98389eed7b0445" - url: "https://github.com/ente-io/computer.git" - source: git - version: "3.2.1" + version: "1.18.0" confetti: dependency: "direct main" description: @@ -218,62 +225,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" - connectivity: + connectivity_plus: dependency: "direct main" description: - name: connectivity - sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8 + name: connectivity_plus + sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" url: "https://pub.dev" source: hosted - version: "3.0.6" - connectivity_for_web: + version: "5.0.2" + connectivity_plus_platform_interface: dependency: transitive description: - name: connectivity_for_web - sha256: "01a390c1d5adc2ed1fa1f52d120c07fe9fd01166a93f965a832fd6cfc0ea6482" + name: connectivity_plus_platform_interface + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a url: "https://pub.dev" source: hosted - version: "0.4.0+1" - connectivity_macos: - dependency: transitive - description: - name: connectivity_macos - sha256: "51ae08d5162eca9669b9d8951ed83ce19c5355a81149f94e4dee2740beb93628" - url: "https://pub.dev" - source: hosted - version: "0.2.1+2" - connectivity_platform_interface: - dependency: transitive - description: - name: connectivity_platform_interface - sha256: "2d82e942df9d49f29a24bb07fb5ce085d4a53e47818c62364d2b6deb9e0d7a8e" - url: "https://pub.dev" - source: hosted - version: "2.0.1" + version: "1.2.4" convert: - dependency: transitive + dependency: "direct main" description: name: convert sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" url: "https://pub.dev" source: hosted version: "3.1.1" - coverage: - dependency: transitive - description: - name: coverage - sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" - url: "https://pub.dev" - source: hosted - version: "1.6.3" cross_file: dependency: transitive description: name: cross_file - sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + sha256: "2f9d2cbccb76127ba28528cb3ae2c2326a122446a83de5a056aaa3880d3882c5" url: "https://pub.dev" source: hosted - version: "0.3.3+4" + version: "0.3.3+7" crypto: dependency: transitive description: @@ -290,38 +273,39 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be - url: "https://pub.dev" - source: hosted - version: "1.0.5" dart_style: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.4" dbus: dependency: transitive description: name: dbus - sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" url: "https://pub.dev" source: hosted - version: "0.7.8" + version: "0.7.10" + desktop_webview_window: + dependency: "direct main" + description: + path: "packages/desktop_webview_window" + ref: HEAD + resolved-ref: "1134cc059de9b574db37ae1f33f4295bad795f56" + url: "https://github.com/MixinNetwork/flutter-plugins" + source: git + version: "0.2.4" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 + sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110" url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "9.1.2" device_info_plus_platform_interface: dependency: transitive description: @@ -330,30 +314,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" - diff_match_patch: - dependency: transitive - description: - name: diff_match_patch - sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" - url: "https://pub.dev" - source: hosted - version: "0.4.1" dio: dependency: "direct main" description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3" url: "https://pub.dev" source: hosted - version: "4.0.6" + version: "5.4.0" dotted_border: dependency: "direct main" description: name: dotted_border - sha256: "07a5c5e8d4e6e992279e190e0352be8faa5b8f96d81c77a78b2d42f060279840" + sha256: "108837e11848ca776c53b30bc870086f84b62ed6e01c503ed976e8f8c7df9c04" url: "https://pub.dev" source: hosted - version: "2.0.0+3" + version: "2.1.0" email_validator: dependency: "direct main" description: @@ -362,6 +338,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.17" + ente_crypto_dart: + dependency: "direct main" + description: + path: "." + ref: HEAD + resolved-ref: e2e66ffd03f23bef5e0bb138b5f01b32d8e9b7bb + url: "https://github.com/ente-io/ente_crypto_dart.git" + source: git + version: "1.0.0" event_bus: dependency: "direct main" description: @@ -395,39 +380,39 @@ packages: source: hosted version: "1.3.1" ffi: - dependency: transitive + dependency: "direct main" description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_picker: dependency: "direct main" description: name: file_picker - sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" + sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6" url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "6.1.1" file_saver: dependency: "direct main" description: name: file_saver - sha256: "591d25e750e3a4b654f7b0293abc2ed857242f82ca7334051b2a8ceeb369dac8" + sha256: "8ffd91ae9f543c5ebbfec71a814ee5aa9e21176d31335133308abf63f4c42e8a" url: "https://pub.dev" source: hosted - version: "0.2.8" + version: "0.2.9" fixnum: - dependency: transitive + dependency: "direct main" description: name: fixnum sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" @@ -467,50 +452,107 @@ packages: dependency: "direct main" description: name: flutter_email_sender - sha256: "9e253c69617f43d4cb5de672e93a7a19c12a21fb6a75e66c6ce7626336c4c1bc" + sha256: "5001e9158f91a8799140fb30a11ad89cd587244f30b4f848d87085985c49b60f" url: "https://pub.dev" source: hosted - version: "5.2.0" + version: "6.0.2" flutter_inappwebview: dependency: "direct main" description: name: flutter_inappwebview - sha256: f73505c792cf083d5566e1a94002311be497d984b5607f25be36d685cf6361cf + sha256: "3e9a443a18ecef966fb930c3a76ca5ab6a7aafc0c7b5e14a4a850cf107b09959" url: "https://pub.dev" source: hosted - version: "5.7.2+3" + version: "6.0.0" + flutter_inappwebview_android: + dependency: transitive + description: + name: flutter_inappwebview_android + sha256: d247f6ed417f1f8c364612fa05a2ecba7f775c8d0c044c1d3b9ee33a6515c421 + url: "https://pub.dev" + source: hosted + version: "1.0.13" + flutter_inappwebview_internal_annotations: + dependency: transitive + description: + name: flutter_inappwebview_internal_annotations + sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter_inappwebview_ios: + dependency: transitive + description: + name: flutter_inappwebview_ios + sha256: f363577208b97b10b319cd0c428555cd8493e88b468019a8c5635a0e4312bd0f + url: "https://pub.dev" + source: hosted + version: "1.0.13" + flutter_inappwebview_macos: + dependency: transitive + description: + name: flutter_inappwebview_macos + sha256: b55b9e506c549ce88e26580351d2c71d54f4825901666bd6cfa4be9415bb2636 + url: "https://pub.dev" + source: hosted + version: "1.0.11" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: "545fd4c25a07d2775f7d5af05a979b2cac4fbf79393b0a7f5d33ba39ba4f6187" + url: "https://pub.dev" + source: hosted + version: "1.0.10" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: d8c680abfb6fec71609a700199635d38a744df0febd5544c5a020bd73de8ee07 + url: "https://pub.dev" + source: hosted + version: "1.0.8" flutter_launcher_icons: dependency: "direct main" description: name: flutter_launcher_icons - sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.9.3" + version: "0.13.1" + flutter_local_authentication: + dependency: "direct main" + description: + path: "." + ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + resolved-ref: "1ac346a04592a05fd75acccf2e01fa3c7e955d96" + url: "https://github.com/eaceto/flutter_local_authentication" + source: git + version: "1.2.0" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - sha256: f222919a34545931e47b06000836b5101baeffb0e6eb5a4691d2d42851740dd9 + sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3 url: "https://pub.dev" source: hosted - version: "12.0.4" + version: "16.3.2" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "3c6d6db334f609a92be0c0915f40871ec56f5d2adf01e77ae364162c587c0ca8" + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "4.0.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "5ec1feac5f7f7d9266759488bc5f76416152baba9aa1b26fe572246caa00d1ab" + sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "7.0.0+1" flutter_localizations: dependency: "direct main" description: flutter @@ -520,99 +562,99 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "6777a3abb974021a39b5fdd2d46a03ca390e03903b6351f21d10e7ecc969f12d" + sha256: "17d9671396fb8ec45ad10f4a975eb8a0f70bedf0fdaf0720b31ea9de6da8c4da" url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.3.7" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.17" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - sha256: "98352186ee7ad3639ccc77ad7924b773ff6883076ab952437d20f18a61f0a7c5" + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.0.0" flutter_secure_storage_linux: - dependency: transitive + dependency: "direct overridden" description: - name: flutter_secure_storage_linux - sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" - url: "https://pub.dev" - source: hosted - version: "1.1.3" + path: flutter_secure_storage_linux + ref: patch-1 + resolved-ref: da8ab43bc51c8c3249a261c33b27aa6f018f819b + url: "https://github.com/prateekmedia/flutter_secure_storage.git" + source: git + version: "1.2.0" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "083add01847fc1c80a07a08e1ed6927e9acd9618a35e330239d4422cd2a58c50" + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe" + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - sha256: fc2910ec9b28d60598216c29ea763b3a96c401f0ce1d13cdf69ccb0e5c93c3ee + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.0" flutter_slidable: dependency: "direct main" description: name: flutter_slidable - sha256: "6c68e1fad129b4b807b2218ef4cf7f7f6f61c5ec8861c990dc2278d9d03cb09f" + sha256: "19ed4813003a6ff4e9c6bcce37e792a2a358919d7603b2b31ff200229191e44c" url: "https://pub.dev" source: hosted - version: "2.0.0" - flutter_sodium: - dependency: "direct main" - description: - path: "." - ref: HEAD - resolved-ref: "267435eaf07af60b94406adf14bedf21e08a6b4f" - url: "https://github.com/ente-io/flutter_sodium.git" - source: git - version: "0.2.0" + version: "3.0.1" flutter_speed_dial: dependency: "direct main" description: name: flutter_speed_dial - sha256: "41d7ad0bc224248637b3a5e0b9083e912a75445bdb450cf82b8ed06d7af7c61d" + sha256: "698a037274a66dbae8697c265440e6acb6ab6cae9ac5f95c749e7944d8f28d41" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "7.0.0" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -627,10 +669,18 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" + sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1 url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "8.2.4" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" frontend_server_client: dependency: transitive description: @@ -663,6 +713,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + gtk: + dependency: transitive + description: + name: gtk + sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + url: "https://pub.dev" + source: hosted + version: "2.1.0" hex: dependency: transitive description: @@ -707,10 +765,10 @@ packages: dependency: transitive description: name: image - sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "4.1.6" intl: dependency: "direct main" description: @@ -747,58 +805,82 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7" + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 url: "https://pub.dev" source: hosted - version: "6.6.2" + version: "6.7.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" lints: dependency: "direct dev" description: name: lints - sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "3.0.0" local_auth: dependency: "direct main" description: name: local_auth - sha256: "7e6c63082e399b61e4af71266b012e767a5d4525dd6e9ba41e174fd42d76e115" + sha256: "27679ed8e0d7daab2357db6bb7076359e083a56b295c0c59723845301da6aed9" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.8" local_auth_android: dependency: "direct main" description: name: local_auth_android - sha256: "523dd636ce061ddb296cbc3db410cb8f21efb7d8798f7b9532c8038ce2f8bad5" + sha256: "54e9c35ce52c06333355ab0d0f41e4c06dbca354b23426765ba41dfb1de27598" url: "https://pub.dev" source: hosted - version: "1.0.31" + version: "1.0.36" local_auth_ios: dependency: "direct main" description: name: local_auth_ios - sha256: edc2977c5145492f3451db9507a2f2f284ee4f408950b3e16670838726761940 + sha256: eb283b530029b334698918f1e282d4483737cbca972ff21b9193be3d6de8e2b8 url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.6" local_auth_platform_interface: dependency: transitive description: name: local_auth_platform_interface - sha256: "9e160d59ef0743e35f1b50f4fb84dc64f55676b1b8071e319ef35e7f3bc13367" + sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.0.10" local_auth_windows: dependency: transitive description: name: local_auth_windows - sha256: "19323b75ab781d5362dbb15dcb7e0916d2431c7a6dbdda016ec9708689877f73" + sha256: "505ba3367ca781efb1c50d3132e44a2446bccc4163427bc203b9b4d8994d97ea" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.10" logging: dependency: "direct main" description: @@ -811,26 +893,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.11.0" mime: dependency: transitive description: @@ -843,10 +925,10 @@ packages: dependency: "direct dev" description: name: mocktail - sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "1.0.3" modal_bottom_sheet: dependency: "direct main" description: @@ -871,22 +953,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - node_preamble: + nm: dependency: transitive description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "0.5.0" open_filex: dependency: "direct main" description: name: open_filex - sha256: "854aefd72dfd74219dc8c8d1767c34ec1eae64b8399a5be317bddb1ec2108915" + sha256: a6c95237767c5647e68b71a476602fcf4f1bfc530c126265e53addae22ef5fc2 url: "https://pub.dev" source: hosted - version: "4.3.2" + version: "4.3.4" otp: dependency: "direct main" description: @@ -907,10 +989,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" + sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" package_info_plus_platform_interface: dependency: transitive description: @@ -928,13 +1010,13 @@ packages: source: hosted version: "0.2.0" path: - dependency: transitive + dependency: "direct main" description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_drawing: dependency: transitive description: @@ -955,90 +1037,90 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.2" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.2.2" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.2" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.1" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" pinput: dependency: "direct main" description: name: pinput - sha256: "27eb69042f75755bdb6544f6e79a50a6ed09d6e97e2d75c8421744df1e392949" + sha256: a92b55ecf9c25d1b9e100af45905385d5bc34fc9b6b04177a9e82cb88fe4d805 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "3.0.1" platform: dependency: transitive description: name: platform - sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.4" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.8" pointycastle: dependency: "direct main" description: name: pointycastle - sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29" url: "https://pub.dev" source: hosted - version: "3.7.3" + version: "3.7.4" pool: dependency: transitive description: @@ -1055,30 +1137,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.0.6" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" protobuf: dependency: "direct main" description: name: protobuf - sha256: "4034a02b7e231e7e60bff30a8ac13a7347abfdac0798595fae0b90a3f0afe759" + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" provider: dependency: transitive description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.1" pub_semver: dependency: transitive description: @@ -1099,10 +1173,10 @@ packages: dependency: transitive description: name: qr - sha256: "5c4208b4dc0d55c3184d10d83ee0ded6212dc2b5e2ba17c5a0c0aab279128d21" + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "3.0.1" qr_code_scanner: dependency: "direct main" description: @@ -1115,34 +1189,42 @@ packages: dependency: "direct main" description: name: qr_flutter - sha256: c5c121c54cb6dd837b9b9d57eb7bc7ec6df4aee741032060c8833a678c80b87e + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.1.0" + screen_retriever: + dependency: transitive + description: + name: screen_retriever + sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + url: "https://pub.dev" + source: hosted + version: "0.1.9" sentry: dependency: "direct main" description: name: sentry - sha256: "39c23342fc96105da449914f7774139a17a0ca8a4e70d9ad5200171f7e47d6ba" + sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25 url: "https://pub.dev" source: hosted - version: "7.9.0" + version: "7.16.0" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: ff68ab31918690da004a42e20204242a3ad9ad57da7e2712da8487060ac9767f + sha256: "6db7fa1b076faf2f5dd77d8cc9ef206171f32a290cc638842d78e5d62b441a27" url: "https://pub.dev" source: hosted - version: "7.9.0" + version: "7.16.0" share_plus: dependency: "direct main" description: name: share_plus - sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd + sha256: "3ef39599b00059db0990ca2e30fca0a29d8b37aae924d60063f8e0184cf20900" url: "https://pub.dev" source: hosted - version: "7.2.1" + version: "7.2.2" share_plus_platform_interface: dependency: transitive description: @@ -1155,58 +1237,58 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: f39696b83e844923b642ce9dd4bd31736c17e697f6731a5adf445b1274cf3cd4 + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.5" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" + sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" shelf: dependency: transitive description: @@ -1215,22 +1297,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e - url: "https://pub.dev" - source: hosted - version: "1.1.2" shelf_web_socket: dependency: transitive description: @@ -1244,14 +1310,38 @@ packages: description: flutter source: sdk version: "0.0.99" + smart_auth: + dependency: transitive + description: + name: smart_auth + sha256: a25229b38c02f733d0a4e98d941b42bed91a976cb589e934895e60ccfa674cf6 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + sodium: + dependency: transitive + description: + name: sodium + sha256: "46c9fef95da0d99f1b0c7981f306b1f9e02627b0513715e47d70177a310c1c22" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + sodium_libs: + dependency: transitive + description: + name: sodium_libs + sha256: "8b1fb9254499aadd2d83824b055f9abaf052307bbc8629e494895ac02d6b867f" + url: "https://pub.dev" + source: hosted + version: "2.2.0+5" source_gen: dependency: transitive description: name: source_gen - sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.5.0" source_helper: dependency: transitive description: @@ -1260,22 +1350,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.4" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" - url: "https://pub.dev" - source: hosted - version: "2.1.1" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" - url: "https://pub.dev" - source: hosted - version: "0.10.12" source_span: dependency: transitive description: @@ -1284,30 +1358,63 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" sqflite: dependency: "direct main" description: - name: sqflite - sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 - url: "https://pub.dev" - source: hosted - version: "2.2.8+4" + path: sqflite + ref: HEAD + resolved-ref: "426e7f25569fee9cfc8ea17fd56c2f405b17f828" + url: "https://github.com/tekartik/sqflite" + source: git + version: "2.3.2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f" + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" url: "https://pub.dev" source: hosted - version: "2.4.5+1" + version: "2.5.3" + sqflite_common_ffi: + dependency: "direct main" + description: + name: sqflite_common_ffi + sha256: "35d2fce1e971707c227cc4775cc017d5eafe06c2654c3435ebd5c3ad6c170f5f" + url: "https://pub.dev" + source: hosted + version: "2.3.0+4" + sqlite3: + dependency: "direct main" + description: + name: sqlite3 + sha256: db65233e6b99e99b2548932f55a987961bc06d82a31a0665451fa0b4fff4c3fb + url: "https://pub.dev" + source: hosted + version: "2.1.0" + sqlite3_flutter_libs: + dependency: "direct main" + description: + name: sqlite3_flutter_libs + sha256: d6c31c8511c441d1f12f20b607343df1afe4eddf24a1cf85021677c8eea26060 + url: "https://pub.dev" + source: hosted + version: "0.5.20" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" step_progress_indicator: dependency: "direct main" description: @@ -1320,10 +1427,10 @@ packages: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -1344,18 +1451,18 @@ packages: dependency: "direct main" description: name: styled_text - sha256: f72928d1ebe8cb149e3b34a689cb1ddca696b808187cf40ac3a0bd183dff379c + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "8.1.0" synchronized: dependency: transitive description: name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -1364,30 +1471,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - test: - dependency: transitive - description: - name: test - sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" - url: "https://pub.dev" - source: hosted - version: "1.24.3" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" - test_core: - dependency: transitive - description: - name: test_core - sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" - url: "https://pub.dev" - source: hosted - version: "0.5.3" + version: "0.6.1" timezone: dependency: transitive description: @@ -1420,134 +1511,118 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" - uni_links: - dependency: "direct main" - description: - name: uni_links - sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - uni_links_platform_interface: - dependency: transitive - description: - name: uni_links_platform_interface - sha256: "929cf1a71b59e3b7c2d8a2605a9cf7e0b125b13bc858e55083d88c62722d4507" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - uni_links_web: - dependency: transitive - description: - name: uni_links_web - sha256: "7539db908e25f67de2438e33cc1020b30ab94e66720b5677ba6763b25f6394df" - url: "https://pub.dev" - source: hosted - version: "0.1.0" universal_io: dependency: transitive description: name: universal_io - sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.2.4" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.0.36" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.2.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.1.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.1" uuid: dependency: "direct main" description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.2.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79 + sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.9+2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced + sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.9+2" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca" + sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.9+2" vector_math: dependency: transitive description: @@ -1560,26 +1635,18 @@ packages: dependency: transitive description: name: vm_service - sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "9.4.0" + version: "13.0.0" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" - web: - dependency: transitive - description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 - url: "https://pub.dev" - source: hosted - version: "0.1.4-beta" + version: "1.1.0" web_socket_channel: dependency: transitive description: @@ -1588,46 +1655,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.0" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" - url: "https://pub.dev" - source: hosted - version: "1.2.0" win32: - dependency: transitive + dependency: "direct main" description: name: win32 - sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + sha256: b0f37db61ba2f2e9b7a78a1caece0052564d1bc70668156cf3a29d676fe4e574 url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "5.1.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + window_manager: + dependency: "direct main" + description: + name: window_manager + sha256: b3c895bdf936c77b83c5254bec2e6b3f066710c1f89c38b20b8acc382b525494 + url: "https://pub.dev" + source: hosted + version: "0.3.8" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" xml: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" xmlstream: dependency: transitive description: name: xmlstream - sha256: "2d10c69a9d5fc46f71798b80ee6db15bc0d5bf560fdbdd264776cbeee0c83631" + sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.1" yaml: dependency: transitive description: @@ -1637,5 +1712,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.2.0-0 <4.0.0" + flutter: ">=3.13.0" diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index c86f1b0b3..cdbb543bc 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -8,45 +8,55 @@ environment: dependencies: adaptive_theme: ^3.1.0 # done + app_links: ^3.5.0 archive: ^3.3.7 base32: ^2.1.3 bip39: ^1.0.6 #done - bloc: ^8.0.3 #done + bloc: ^8.1.2 clipboard: ^0.1.3 collection: # dart - computer: - git: "https://github.com/ente-io/computer.git" confetti: ^0.7.0 - connectivity: ^3.0.3 - cupertino_icons: ^1.0.0 - device_info_plus: ^8.0.0 - dio: ^4.0.6 + connectivity_plus: ^5.0.2 + convert: ^3.1.1 + desktop_webview_window: + git: + url: https://github.com/MixinNetwork/flutter-plugins + path: packages/desktop_webview_window + device_info_plus: ^9.1.1 + dio: ^5.4.0 dotted_border: ^2.0.0+2 email_validator: ^2.0.1 + ente_crypto_dart: + git: + url: https://github.com/ente-io/ente_crypto_dart.git event_bus: ^2.0.0 expandable: ^5.0.1 expansion_tile_card: ^3.0.0 - file_picker: ^5.2.4 + ffi: ^2.1.0 + file_picker: ^6.1.1 # https://github.com/incrediblezayed/file_saver/issues/86 - file_saver: 0.2.8 + file_saver: ^0.2.9 + fixnum: ^1.1.0 fk_user_agent: ^2.1.0 flutter: sdk: flutter flutter_bloc: ^8.0.1 flutter_displaymode: ^0.6.0 - flutter_email_sender: ^5.1.0 - flutter_inappwebview: ^5.7.1 - flutter_launcher_icons: ^0.9.3 - flutter_local_notifications: ^12.0.3 + flutter_email_sender: ^6.0.2 + flutter_inappwebview: ^6.0.0 + flutter_launcher_icons: ^0.13.1 + flutter_local_authentication: + git: + url: https://github.com/eaceto/flutter_local_authentication + ref: 1ac346a04592a05fd75acccf2e01fa3c7e955d96 + flutter_local_notifications: ^16.3.1+1 flutter_localizations: sdk: flutter flutter_native_splash: ^2.2.13 - flutter_secure_storage: ^8.0.0 - flutter_slidable: ^2.0.0 - flutter_sodium: - git: - url: https://github.com/ente-io/flutter_sodium.git - flutter_speed_dial: ^6.2.0 + flutter_secure_storage: ^9.0.0 + flutter_slidable: ^3.0.1 + flutter_speed_dial: ^7.0.0 + flutter_staggered_grid_view: ^0.7.0 flutter_svg: ^2.0.5 fluttertoast: ^8.1.1 google_nav_bar: ^5.0.5 #supported @@ -64,33 +74,46 @@ dependencies: otp: ^3.1.1 package_info_plus: ^4.1.0 password_strength: ^0.2.0 + path: ^1.8.3 path_provider: ^2.0.11 - pinput: ^1.2.2 + pinput: ^3.0.1 pointycastle: ^3.7.3 privacy_screen: ^0.0.6 protobuf: ^3.0.0 qr_code_scanner: ^1.0.1 - qr_flutter: 4.0.0 + qr_flutter: ^4.1.0 sentry: ^7.9.0 sentry_flutter: ^7.9.0 share_plus: ^7.2.1 shared_preferences: ^2.0.5 - sqflite: ^2.1.0 + sqflite: + git: + url: https://github.com/tekartik/sqflite + path: sqflite + sqflite_common_ffi: ^2.3.0+4 + sqlite3: ^2.1.0 + sqlite3_flutter_libs: ^0.5.19+1 step_progress_indicator: ^1.0.2 - styled_text: ^7.0.0 + styled_text: ^8.1.0 tuple: ^2.0.0 - uni_links: ^0.5.1 url_launcher: ^6.1.5 - uuid: ^3.0.4 + uuid: ^4.2.2 + win32: ^5.1.1 + window_manager: ^0.3.8 +dependency_overrides: + flutter_secure_storage_linux: + git: + url: https://github.com/prateekmedia/flutter_secure_storage.git + ref: patch-1 + path: flutter_secure_storage_linux dev_dependencies: - bloc_test: ^9.0.3 build_runner: ^2.1.11 flutter_test: sdk: flutter json_serializable: ^6.2.0 - lints: ^1.0.1 - mocktail: ^0.3.0 + lints: ^3.0.0 + mocktail: ^1.0.3 # The following section is specific to Flutter. flutter: diff --git a/auth/web/index.html b/auth/web/index.html index 998d81d85..ef953df53 100644 --- a/auth/web/index.html +++ b/auth/web/index.html @@ -21,13 +21,13 @@ - + - My App + Auth diff --git a/auth/web/manifest.json b/auth/web/manifest.json index 232359953..eaf5b0fab 100644 --- a/auth/web/manifest.json +++ b/auth/web/manifest.json @@ -1,6 +1,6 @@ { - "name": "My App", - "short_name": "My App", + "name": "Auth", + "short_name": "Auth", "start_url": ".", "display": "standalone", "background_color": "#0175C2", diff --git a/auth/windows/CMakeLists.txt b/auth/windows/CMakeLists.txt index d481d1864..4dcccb5fb 100644 --- a/auth/windows/CMakeLists.txt +++ b/auth/windows/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) -project(ente_auth LANGUAGES CXX) +project(auth LANGUAGES CXX) -set(BINARY_NAME "ente_auth") +set(BINARY_NAME "auth") cmake_policy(SET CMP0063 NEW) diff --git a/auth/windows/flutter/CMakeLists.txt b/auth/windows/flutter/CMakeLists.txt index b2e4bd8d6..4f2af69bb 100644 --- a/auth/windows/flutter/CMakeLists.txt +++ b/auth/windows/flutter/CMakeLists.txt @@ -9,6 +9,11 @@ include(${EPHEMERAL_DIR}/generated_config.cmake) # https://github.com/flutter/flutter/issues/57146. set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + # === Flutter Library === set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") @@ -91,7 +96,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E env ${FLUTTER_TOOL_ENVIRONMENT} "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" - windows-x64 $ + ${FLUTTER_TARGET_PLATFORM} $ VERBATIM ) add_custom_target(flutter_assemble DEPENDS diff --git a/auth/windows/flutter/generated_plugin_registrant.cc b/auth/windows/flutter/generated_plugin_registrant.cc index 20f612aa8..f6197f8ce 100644 --- a/auth/windows/flutter/generated_plugin_registrant.cc +++ b/auth/windows/flutter/generated_plugin_registrant.cc @@ -6,24 +6,51 @@ #include "generated_plugin_registrant.h" +#include +#include +#include #include +#include #include #include +#include #include #include +#include +#include +#include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + DesktopWebviewWindowPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopWebviewWindowPlugin")); FileSaverPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSaverPlugin")); + FlutterLocalAuthenticationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLocalAuthenticationPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + ScreenRetrieverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); SentryFlutterPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("SentryFlutterPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + SmartAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SmartAuthPlugin")); + SodiumLibsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SodiumLibsPluginCApi")); + Sqlite3FlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WindowManagerPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WindowManagerPlugin")); } diff --git a/auth/windows/flutter/generated_plugins.cmake b/auth/windows/flutter/generated_plugins.cmake index 41d3e5061..985932151 100644 --- a/auth/windows/flutter/generated_plugins.cmake +++ b/auth/windows/flutter/generated_plugins.cmake @@ -3,12 +3,21 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links + connectivity_plus + desktop_webview_window file_saver + flutter_local_authentication flutter_secure_storage_windows local_auth_windows + screen_retriever sentry_flutter share_plus + smart_auth + sodium_libs + sqlite3_flutter_libs url_launcher_windows + window_manager ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/auth/windows/packaging/exe/make_config.yaml b/auth/windows/packaging/exe/make_config.yaml new file mode 100644 index 000000000..e4865decc --- /dev/null +++ b/auth/windows/packaging/exe/make_config.yaml @@ -0,0 +1,8 @@ +app_id: 9E5F0C93-96A3-4DA9-AE52-1AA6339851FC +publisher: ente.io +publisher_url: https://github.com/ente-io/auth +display_name: ente Auth +create_desktop_icon: true +install_dir_name: enteauth +locales: + - en diff --git a/auth/windows/runner/Runner.rc b/auth/windows/runner/Runner.rc index 6c586f270..8b36e6b4e 100644 --- a/auth/windows/runner/Runner.rc +++ b/auth/windows/runner/Runner.rc @@ -60,16 +60,16 @@ IDI_APP_ICON ICON "resources\\app_icon.ico" // Version // -#ifdef FLUTTER_BUILD_NUMBER -#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD #else -#define VERSION_AS_NUMBER 1,0,0 +#define VERSION_AS_NUMBER 1,0,0,0 #endif -#ifdef FLUTTER_BUILD_NAME -#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION #else -#define VERSION_AS_STRING "1.0.0" +#define VERSION_AS_STRING "2.0.31" #endif VS_VERSION_INFO VERSIONINFO @@ -90,12 +90,12 @@ BEGIN BLOCK "040904e4" BEGIN VALUE "CompanyName", "Ente Technologies, Inc." "\0" - VALUE "FileDescription", "ente Authenticator" "\0" + VALUE "FileDescription", "ente Auth" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "ente Authenticator" "\0" + VALUE "InternalName", "ente Auth" "\0" VALUE "LegalCopyright", "Copyright (C) 2022 Ente Technologies, Inc.. All rights reserved." "\0" - VALUE "OriginalFilename", "ente_auth.exe" "\0" - VALUE "ProductName", "ente Authenticator" "\0" + VALUE "OriginalFilename", "auth.exe" "\0" + VALUE "ProductName", "ente Auth" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/auth/windows/runner/main.cpp b/auth/windows/runner/main.cpp index df82ae0a5..6960d6b68 100644 --- a/auth/windows/runner/main.cpp +++ b/auth/windows/runner/main.cpp @@ -6,10 +6,12 @@ #include "utils.h" int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, - _In_ wchar_t *command_line, _In_ int show_command) { + _In_ wchar_t *command_line, _In_ int show_command) +{ // Attach to console when present (e.g., 'flutter run') or create a // new console when running with a debugger. - if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) + { CreateAndAttachConsole(); } @@ -27,13 +29,15 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"My App", origin, size)) { + if (!window.CreateAndShow(L"ente Auth", origin, size)) + { return EXIT_FAILURE; } window.SetQuitOnClose(true); ::MSG msg; - while (::GetMessage(&msg, nullptr, 0, 0)) { + while (::GetMessage(&msg, nullptr, 0, 0)) + { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } diff --git a/auth/windows/runner/resources/app_icon.ico b/auth/windows/runner/resources/app_icon.ico index c04e20caf..6f45f522b 100644 Binary files a/auth/windows/runner/resources/app_icon.ico and b/auth/windows/runner/resources/app_icon.ico differ diff --git a/auth/windows/runner/win32_window.cpp b/auth/windows/runner/win32_window.cpp index c10f08dc7..26c85a0db 100644 --- a/auth/windows/runner/win32_window.cpp +++ b/auth/windows/runner/win32_window.cpp @@ -3,48 +3,57 @@ #include #include "resource.h" +#include "app_links/app_links_plugin_c_api.h" -namespace { +namespace +{ -constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; -// The number of Win32Window objects that currently exist. -static int g_active_window_count = 0; + // The number of Win32Window objects that currently exist. + static int g_active_window_count = 0; -using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); -// Scale helper to convert logical scaler values to physical using passed in -// scale factor -int Scale(int source, double scale_factor) { - return static_cast(source * scale_factor); -} - -// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. -// This API is only needed for PerMonitor V1 awareness mode. -void EnableFullDpiSupportIfAvailable(HWND hwnd) { - HMODULE user32_module = LoadLibraryA("User32.dll"); - if (!user32_module) { - return; + // Scale helper to convert logical scaler values to physical using passed in + // scale factor + int Scale(int source, double scale_factor) + { + return static_cast(source * scale_factor); } - auto enable_non_client_dpi_scaling = - reinterpret_cast( - GetProcAddress(user32_module, "EnableNonClientDpiScaling")); - if (enable_non_client_dpi_scaling != nullptr) { - enable_non_client_dpi_scaling(hwnd); - FreeLibrary(user32_module); - } -} -} // namespace + // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. + // This API is only needed for PerMonitor V1 awareness mode. + void EnableFullDpiSupportIfAvailable(HWND hwnd) + { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) + { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) + { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } + } + +} // namespace // Manages the Win32Window's window class registration. -class WindowClassRegistrar { - public: +class WindowClassRegistrar +{ +public: ~WindowClassRegistrar() = default; // Returns the singleton registar instance. - static WindowClassRegistrar* GetInstance() { - if (!instance_) { + static WindowClassRegistrar *GetInstance() + { + if (!instance_) + { instance_ = new WindowClassRegistrar(); } return instance_; @@ -52,24 +61,26 @@ class WindowClassRegistrar { // Returns the name of the window class, registering the class if it hasn't // previously been registered. - const wchar_t* GetWindowClass(); + const wchar_t *GetWindowClass(); // Unregisters the window class. Should only be called if there are no // instances of the window. void UnregisterWindowClass(); - private: +private: WindowClassRegistrar() = default; - static WindowClassRegistrar* instance_; + static WindowClassRegistrar *instance_; bool class_registered_ = false; }; -WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; +WindowClassRegistrar *WindowClassRegistrar::instance_ = nullptr; -const wchar_t* WindowClassRegistrar::GetWindowClass() { - if (!class_registered_) { +const wchar_t *WindowClassRegistrar::GetWindowClass() +{ + if (!class_registered_) + { WNDCLASS window_class{}; window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); window_class.lpszClassName = kWindowClassName; @@ -88,26 +99,34 @@ const wchar_t* WindowClassRegistrar::GetWindowClass() { return kWindowClassName; } -void WindowClassRegistrar::UnregisterWindowClass() { +void WindowClassRegistrar::UnregisterWindowClass() +{ UnregisterClass(kWindowClassName, nullptr); class_registered_ = false; } -Win32Window::Win32Window() { +Win32Window::Win32Window() +{ ++g_active_window_count; } -Win32Window::~Win32Window() { +Win32Window::~Win32Window() +{ --g_active_window_count; Destroy(); } -bool Win32Window::CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size) { +bool Win32Window::CreateAndShow(const std::wstring &title, + const Point &origin, + const Size &size) +{ + if (SendAppLinkToInstance(title)) + { + return false; + } Destroy(); - const wchar_t* window_class = + const wchar_t *window_class = WindowClassRegistrar::GetInstance()->GetWindowClass(); const POINT target_point = {static_cast(origin.x), @@ -117,32 +136,38 @@ bool Win32Window::CreateAndShow(const std::wstring& title, double scale_factor = dpi / 96.0; HWND window = CreateWindow( - window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + window_class, title.c_str(), + WS_OVERLAPPEDWINDOW, // do not add WS_VISIBLE since the window will be shown later Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), Scale(size.width, scale_factor), Scale(size.height, scale_factor), nullptr, nullptr, GetModuleHandle(nullptr), this); - if (!window) { - return false; - } + if (!window) + { + return false; + } - return OnCreate(); + return OnCreate(); } // static LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept { - if (message == WM_NCCREATE) { - auto window_struct = reinterpret_cast(lparam); + LPARAM const lparam) noexcept +{ + if (message == WM_NCCREATE) + { + auto window_struct = reinterpret_cast(lparam); SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(window_struct->lpCreateParams)); - auto that = static_cast(window_struct->lpCreateParams); + auto that = static_cast(window_struct->lpCreateParams); EnableFullDpiSupportIfAvailable(window); that->window_handle_ = window; - } else if (Win32Window* that = GetThisFromHandle(window)) { + } + else if (Win32Window *that = GetThisFromHandle(window)) + { return that->MessageHandler(window, message, wparam, lparam); } @@ -153,64 +178,76 @@ LRESULT Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam, - LPARAM const lparam) noexcept { - switch (message) { - case WM_DESTROY: - window_handle_ = nullptr; - Destroy(); - if (quit_on_close_) { - PostQuitMessage(0); - } - return 0; - - case WM_DPICHANGED: { - auto newRectSize = reinterpret_cast(lparam); - LONG newWidth = newRectSize->right - newRectSize->left; - LONG newHeight = newRectSize->bottom - newRectSize->top; - - SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, - newHeight, SWP_NOZORDER | SWP_NOACTIVATE); - - return 0; - } - case WM_SIZE: { - RECT rect = GetClientArea(); - if (child_content_ != nullptr) { - // Size and position the child window. - MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, - rect.bottom - rect.top, TRUE); - } - return 0; + LPARAM const lparam) noexcept +{ + switch (message) + { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) + { + PostQuitMessage(0); } + return 0; - case WM_ACTIVATE: - if (child_content_ != nullptr) { - SetFocus(child_content_); - } - return 0; + case WM_DPICHANGED: + { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: + { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) + { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) + { + SetFocus(child_content_); + } + return 0; } return DefWindowProc(window_handle_, message, wparam, lparam); } -void Win32Window::Destroy() { +void Win32Window::Destroy() +{ OnDestroy(); - if (window_handle_) { + if (window_handle_) + { DestroyWindow(window_handle_); window_handle_ = nullptr; } - if (g_active_window_count == 0) { + if (g_active_window_count == 0) + { WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); } } -Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { - return reinterpret_cast( +Win32Window *Win32Window::GetThisFromHandle(HWND const window) noexcept +{ + return reinterpret_cast( GetWindowLongPtr(window, GWLP_USERDATA)); } -void Win32Window::SetChildContent(HWND content) { +void Win32Window::SetChildContent(HWND content) +{ child_content_ = content; SetParent(content, window_handle_); RECT frame = GetClientArea(); @@ -221,25 +258,68 @@ void Win32Window::SetChildContent(HWND content) { SetFocus(child_content_); } -RECT Win32Window::GetClientArea() { +RECT Win32Window::GetClientArea() +{ RECT frame; GetClientRect(window_handle_, &frame); return frame; } -HWND Win32Window::GetHandle() { +HWND Win32Window::GetHandle() +{ return window_handle_; } -void Win32Window::SetQuitOnClose(bool quit_on_close) { +void Win32Window::SetQuitOnClose(bool quit_on_close) +{ quit_on_close_ = quit_on_close; } -bool Win32Window::OnCreate() { +bool Win32Window::OnCreate() +{ // No-op; provided for subclasses. return true; } -void Win32Window::OnDestroy() { +void Win32Window::OnDestroy() +{ // No-op; provided for subclasses. } + +bool Win32Window::SendAppLinkToInstance(const std::wstring &title) +{ + // Find our exact window + HWND hwnd = ::FindWindow(kWindowClassName, title.c_str()); + + if (hwnd) + { + // Dispatch new link to current window + SendAppLink(hwnd); + + // (Optional) Restore our window to front in same state + WINDOWPLACEMENT place = {sizeof(WINDOWPLACEMENT)}; + GetWindowPlacement(hwnd, &place); + + switch (place.showCmd) + { + case SW_SHOWMAXIMIZED: + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + break; + case SW_SHOWMINIMIZED: + ShowWindow(hwnd, SW_RESTORE); + break; + default: + ShowWindow(hwnd, SW_NORMAL); + break; + } + + SetWindowPos(0, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); + SetForegroundWindow(hwnd); + // END Restore + + // Window has been found, don't create another one. + return true; + } + + return false; +} \ No newline at end of file diff --git a/auth/windows/runner/win32_window.h b/auth/windows/runner/win32_window.h index 17ba43112..9d36b0da3 100644 --- a/auth/windows/runner/win32_window.h +++ b/auth/windows/runner/win32_window.h @@ -10,15 +10,18 @@ // A class abstraction for a high DPI-aware Win32 Window. Intended to be // inherited from by classes that wish to specialize with custom // rendering and input handling -class Win32Window { - public: - struct Point { +class Win32Window +{ +public: + struct Point + { unsigned int x; unsigned int y; Point(unsigned int x, unsigned int y) : x(x), y(y) {} }; - struct Size { + struct Size + { unsigned int width; unsigned int height; Size(unsigned int width, unsigned int height) @@ -34,9 +37,9 @@ class Win32Window { // consistent size to will treat the width height passed in to this function // as logical pixels and scale to appropriate for the default monitor. Returns // true if the window was created successfully. - bool CreateAndShow(const std::wstring& title, - const Point& origin, - const Size& size); + bool CreateAndShow(const std::wstring &title, + const Point &origin, + const Size &size); // Release OS resources associated with window. void Destroy(); @@ -54,7 +57,7 @@ class Win32Window { // Return a RECT representing the bounds of the current client area. RECT GetClientArea(); - protected: +protected: // Processes and route salient window messages for mouse handling, // size change and DPI. Delegates handling of these to member overloads that // inheriting classes can handle. @@ -70,9 +73,14 @@ class Win32Window { // Called when Destroy is called. virtual void OnDestroy(); - private: +private: friend class WindowClassRegistrar; + // Dispatches link if any. + // This method enables our app to be with a single instance too. + // This is mandatory if you want to catch further links in same app. + bool SendAppLinkToInstance(const std::wstring &title); + // OS callback called by message pump. Handles the WM_NCCREATE message which // is passed when the non-client area is being created and enables automatic // non-client DPI scaling so that the non-client area automatically @@ -84,7 +92,7 @@ class Win32Window { LPARAM const lparam) noexcept; // Retrieves a class instance pointer for |window| - static Win32Window* GetThisFromHandle(HWND const window) noexcept; + static Win32Window *GetThisFromHandle(HWND const window) noexcept; bool quit_on_close_ = false; @@ -95,4 +103,4 @@ class Win32Window { HWND child_content_ = nullptr; }; -#endif // RUNNER_WIN32_WINDOW_H_ +#endif // RUNNER_WIN32_WINDOW_H_