Compare commits
34 Commits
decoded_im
...
send_ott
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31057cbe9e | ||
|
|
fc1096c985 | ||
|
|
4b5f91a428 | ||
|
|
29b12fc6b5 | ||
|
|
c3eec71d60 | ||
|
|
2fccdee0d6 | ||
|
|
8f053e7a7b | ||
|
|
de7291f5d4 | ||
|
|
e09c952198 | ||
|
|
86a9dee49d | ||
|
|
33b1a7e4f8 | ||
|
|
de783e91dc | ||
|
|
554c8f4b7a | ||
|
|
f438142646 | ||
|
|
aa6c010562 | ||
|
|
2830c89bde | ||
|
|
4de530b882 | ||
|
|
62d7c87dc7 | ||
|
|
3a1b6dbf15 | ||
|
|
5e0bba390b | ||
|
|
df6a3b94db | ||
|
|
72b7e12768 | ||
|
|
3a7d82a799 | ||
|
|
5f1cfb9ba5 | ||
|
|
298e3695c7 | ||
|
|
621713d0b4 | ||
|
|
34813d2fae | ||
|
|
d8e4418d78 | ||
|
|
9771a5bc5d | ||
|
|
aa4207f878 | ||
|
|
f2049ac7fa | ||
|
|
cf938eca91 | ||
|
|
a3d3ee24f8 | ||
|
|
6b37cc46a5 |
@@ -142,6 +142,22 @@ var _updateFreeUserStorage = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var _sendMail = &cobra.Command{
|
||||
Use: "send-mail <to-email> <from-email> <from-name>",
|
||||
Args: cobra.ExactArgs(3),
|
||||
Short: "Sends a test mail via the admin api",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
recoverWithLog()
|
||||
var flags = &model.AdminActionForUser{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Name == "admin-user" {
|
||||
flags.AdminEmail = f.Value.String()
|
||||
}
|
||||
})
|
||||
return ctrl.SendTestMail(context.Background(), *flags, args[0], args[1], args[2])
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(_adminCmd)
|
||||
_ = _userDetailsCmd.MarkFlagRequired("admin-user")
|
||||
@@ -159,5 +175,6 @@ func init() {
|
||||
_updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)")
|
||||
// add a flag with no value --no-limit
|
||||
_updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser)
|
||||
_sendMail.Flags().StringP("admin-user", "a", "", "The email of the admin user. ")
|
||||
_adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser, _sendMail)
|
||||
}
|
||||
|
||||
@@ -139,5 +139,28 @@ func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.User
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Client) SendTestMail(ctx context.Context, toEmail, fromEmail, fromName string) error {
|
||||
body := map[string]interface{}{
|
||||
"to": []string{toEmail},
|
||||
"fromName": fromName,
|
||||
"fromEmail": fromEmail,
|
||||
"subject": "Test mail from Ente",
|
||||
"body": "This is a test mail from Ente",
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
SetBody(body).
|
||||
Post("/admin/mail")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.IsError() {
|
||||
return &ApiError{
|
||||
StatusCode: r.StatusCode(),
|
||||
Message: r.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -156,6 +156,23 @@ func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActio
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) SendTestMail(ctx context.Context, params model.AdminActionForUser, to, from, fromName string) error {
|
||||
accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Client.SendTestMail(accountCtx, to, from, fromName)
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*api.ApiError); ok && apiErr.StatusCode == 400 && strings.Contains(apiErr.Message, "Token is too old") {
|
||||
fmt.Printf("Error: old admin token, please re-authenticate using `ente account add` \n")
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Successfully sent test email to %s\n", to)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ClICtrl) buildAdminContext(ctx context.Context, adminEmail string) (context.Context, error) {
|
||||
accounts, err := c.GetAccounts(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -171,6 +171,8 @@ smtp:
|
||||
email:
|
||||
# Optional name for sender
|
||||
sender-name:
|
||||
# Optional encryption
|
||||
encryption:
|
||||
```
|
||||
|
||||
| Variable | Description | Default |
|
||||
@@ -181,6 +183,7 @@ smtp:
|
||||
| `smtp.password` | SMTP auth password | |
|
||||
| `smtp.email` | Sender email address | |
|
||||
| `smtp.sender-name` | Custom name for email sender | |
|
||||
| `smtp.encryption` | Encryption method (tls, ssl) | |
|
||||
| `transmail.key` | Zeptomail API key | |
|
||||
|
||||
### WebAuthn Passkey Support
|
||||
|
||||
1
mobile/apps/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
surprise/
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Χρήση κλειδιού ανάκτησης",
|
||||
"incorrectPasswordTitle": "Λάθος κωδικός πρόσβασης",
|
||||
"welcomeBack": "Καλωσορίσατε και πάλι!",
|
||||
"emailAlreadyRegistered": "Το email είναι ήδη καταχωρημένο.",
|
||||
"emailNotRegistered": "Το email δεν έχει καταχωρηθεί.",
|
||||
"madeWithLoveAtPrefix": "φτιαγμένη με ❤️ στο ",
|
||||
"supportDevs": "Εγγραφείτε στο <bold-green>ente</bold-green> για να μας υποστηρίξετε",
|
||||
"supportDiscount": "Χρησιμοποιήστε τον κωδικό κουπονιού \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
@@ -171,6 +173,7 @@
|
||||
"invalidQRCode": "Μη έγκυρος κωδικός QR",
|
||||
"noRecoveryKeyTitle": "Χωρίς κλειδί ανάκτησης;",
|
||||
"enterEmailHint": "Εισάγετε τη διεύθυνση email σας",
|
||||
"enterNewEmailHint": "Εισάγετε την διεύθυνση ηλ. ταχυδρομείου σας",
|
||||
"invalidEmailTitle": "Μη έγκυρη διεύθυνση email",
|
||||
"invalidEmailMessage": "Παρακαλούμε εισάγετε μια έγκυρη διεύθυνση email.",
|
||||
"deleteAccount": "Διαγραφή λογαριασμού",
|
||||
@@ -258,6 +261,10 @@
|
||||
"areYouSureYouWantToLogout": "Είστε σίγουροι ότι θέλετε να αποσυνδεθείτε;",
|
||||
"yesLogout": "Ναι, αποσύνδεση",
|
||||
"exit": "Εξοδος",
|
||||
"theme": "Θέμα",
|
||||
"lightTheme": "Φωτεινό",
|
||||
"darkTheme": "Σκοτεινό",
|
||||
"systemTheme": "Σύστημα",
|
||||
"verifyingRecoveryKey": "Επαλήθευση κλειδιού ανάκτησης...",
|
||||
"recoveryKeyVerified": "Το κλειδί ανάκτησης επαληθεύτηκε",
|
||||
"recoveryKeySuccessBody": "Τέλεια! Το κλειδί ανάκτησης σας είναι έγκυρο. Σας ευχαριστούμε για την επαλήθευση.\n\nΠαρακαλώ θυμηθείτε να κρατήσετε το κλειδί ανάκτησης σας και σε αντίγραφο ασφαλείας.",
|
||||
@@ -490,5 +497,24 @@
|
||||
"appLockNotEnabled": "Το κλείδωμα εφαρμογής δεν είναι ενεργοποιημένο",
|
||||
"appLockNotEnabledDescription": "Παρακαλώ ενεργοποιήστε το κλείδωμα εφαρμογής μέσω της επιλογής Ασφάλεια > Κλείδωμα εφαρμογής",
|
||||
"authToViewPasskey": "Παρακαλώ πιστοποιηθείτε για να δείτε το κλειδί πρόσβασης",
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας."
|
||||
"appLockOfflineModeWarning": "Έχετε επιλέξει να προχωρήσετε χωρίς αντίγραφα ασφαλείας. Αν ξεχάσετε τον κωδικό της εφαρμογής, θα κλειδωθείτε από την πρόσβαση στα δεδομένα σας.",
|
||||
"duplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"noDuplicates": "✨ Δεν υπάρχουν διπλότυπα",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Δεν υπάρχουν διπλότυπα αρχεία που μπορούν να εκκαθαριστούν",
|
||||
"deduplicateCodes": "Διπλότυποι κωδικοί",
|
||||
"deselectAll": "Αποεπιλογή όλων",
|
||||
"selectAll": "Επιλογή όλων",
|
||||
"deleteDuplicates": "Διαγραφή διπλότυπων",
|
||||
"plainHTML": "Απλό HTML",
|
||||
"dropReviewiOS": "Αφήστε μια κριτική στο App Store",
|
||||
"dropReviewAndroid": "Αφήστε μια κριτική στο Play Store",
|
||||
"giveUsAStarOnGithub": "Δώστε μας ένα αστέρι στο Github",
|
||||
"free5GB": "5GB δωρεάν στο <bold-green>ente</bold-green> Photos",
|
||||
"freeStorageOffer": "10% έκπτωση στο <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Χρησιμοποιήστε τον κωδικό \"AUTH\" για να λάβετε 10% έκπτωση για τον πρώτο χρόνο",
|
||||
"advanced": "Για προχωρημένους",
|
||||
"algorithm": "Αλγόριθμος",
|
||||
"type": "Τύπος",
|
||||
"period": "Περίοδος",
|
||||
"digits": "Ψηφία"
|
||||
}
|
||||
@@ -45,7 +45,7 @@
|
||||
"timeBasedKeyType": "Oparte na czasie (TOTP)",
|
||||
"counterBasedKeyType": "Oparte na liczniku (HOTP)",
|
||||
"saveAction": "Zapisz",
|
||||
"nextTotpTitle": "następny",
|
||||
"nextTotpTitle": "dalej",
|
||||
"deleteCodeTitle": "Usunąć kod?",
|
||||
"deleteCodeMessage": "Czy na pewno chcesz usunąć ten kod? Ta akcja jest nieodwracalna.",
|
||||
"trashCode": "Przenieść kod do kosza?",
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
# soon.
|
||||
# Ente Locker
|
||||
|
||||
## TODOs
|
||||
|
||||
Refactor and merge
|
||||
- [ ] Verify correctness for `PackageInfoUtil.getPackageName()` on Linux and Windows
|
||||
- [ ] Update `file_url.dart` to download only via CF worker when necessary
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<application
|
||||
android:label="Ente Locker"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/icon_blue">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="16%" />
|
||||
</foreground>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#1071ff</color>
|
||||
</resources>
|
||||
BIN
mobile/apps/locker/assets/2.0x/broken_heart.png
Normal file
|
After Width: | Height: | Size: 367 KiB |
BIN
mobile/apps/locker/assets/3.0x/broken_heart.png
Normal file
|
After Width: | Height: | Size: 680 KiB |
BIN
mobile/apps/locker/assets/broken_heart.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
mobile/apps/locker/assets/launcher-icons/icon-blue.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
mobile/apps/locker/assets/launcher-icons/icon-foreground.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
@@ -75,9 +75,9 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- privacy_screen (0.0.1):
|
||||
- Flutter
|
||||
- SDWebImage (5.21.0):
|
||||
- SDWebImage/Core (= 5.21.0)
|
||||
- SDWebImage/Core (5.21.0)
|
||||
- SDWebImage (5.21.1):
|
||||
- SDWebImage/Core (= 5.21.1)
|
||||
- SDWebImage/Core (5.21.1)
|
||||
- Sentry/HybridSDK (8.46.0)
|
||||
- sentry_flutter (8.14.2):
|
||||
- Flutter
|
||||
@@ -209,7 +209,7 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 2df8b0aab7e4aba81261c230cbea31c82a62dd1b
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
|
||||
@@ -622,7 +622,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -929,7 +929,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
@@ -956,7 +956,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = IconBlue;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -0,0 +1 @@
|
||||
{"images":[{"size":"20x20","idiom":"iphone","filename":"IconBlue-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconBlue-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconBlue-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconBlue-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconBlue-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"IconBlue-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconBlue-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconBlue-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconBlue-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconBlue-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconBlue-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconBlue-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconBlue-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconBlue-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconBlue-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"IconBlue-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconBlue-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconBlue-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconBlue-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconBlue-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconBlue-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconBlue-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconBlue-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -9,6 +9,10 @@ const int android11SDKINT = 30;
|
||||
const mnemonicKeyWordCount = 24;
|
||||
|
||||
const kDefaultProductionEndpoint = 'https://api.ente.io';
|
||||
const String githubDiscussionsUrl =
|
||||
"https://github.com/ente-io/ente/discussions";
|
||||
|
||||
const supportEmail = 'support@ente.io';
|
||||
|
||||
final tempDirCleanUpInterval = kDebugMode
|
||||
? const Duration(hours: 1).inMicroseconds
|
||||
|
||||
@@ -301,12 +301,53 @@
|
||||
"failedToCreateShareLink": "Failed to create link",
|
||||
"failedToDeleteShareLink": "Failed to delete link",
|
||||
"deletingFile": "Deleting file...",
|
||||
"addInformation": "Add information",
|
||||
"addInformationDialogSubtitle": "Choose the type of information you want to add",
|
||||
"physicalDocument": "Physical document",
|
||||
"physicalDocumentDescription": "Save information about documents and items in the real world.",
|
||||
"emergencyContact": "Emergency contact",
|
||||
"emergencyContactDescription": "Save information about important contacts.",
|
||||
"accountCredential": "Account credential",
|
||||
"accountCredentialDescription": "Save information about your important account credentials."
|
||||
"changeEmail": "Change email",
|
||||
"authToChangeYourEmail": "Please authenticate to change your email",
|
||||
"changePasswordTitle": "Change password",
|
||||
"authToChangeYourPassword": "Please authenticate to change your password",
|
||||
"recoveryKey": "Recovery key",
|
||||
"ok": "Ok",
|
||||
"logout": "Logout",
|
||||
"deleteAccount": "Delete account",
|
||||
"areYouSureYouWantToLogout": "Are you sure you want to logout?",
|
||||
"yesLogout": "Yes, logout",
|
||||
"changePassword": "Change password",
|
||||
"authToViewYourRecoveryKey": "Please authenticate to view your recovery key",
|
||||
"account": "Account",
|
||||
"security": "Security",
|
||||
"emailVerificationToggle": "Email verification",
|
||||
"authToChangeEmailVerificationSetting": "Please authenticate to change email verification",
|
||||
"passkey": "Passkey",
|
||||
"authenticateGeneric": "Please authenticate",
|
||||
"somethingWentWrong": "Something went wrong",
|
||||
"appLock": "App lock",
|
||||
"warning": "Warning",
|
||||
"appLockOfflineModeWarning": "You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data.",
|
||||
"authToChangeLockscreenSetting": "Please authenticate to change lockscreen setting",
|
||||
"authToViewPasskey": "Please authenticate to view passkey",
|
||||
"theme": "Theme",
|
||||
"lightTheme": "Light",
|
||||
"darkTheme": "Dark",
|
||||
"systemTheme": "System",
|
||||
"settings": "Settings",
|
||||
"about": "About",
|
||||
"weAreOpenSource": "We are open source!",
|
||||
"privacy": "Privacy",
|
||||
"terms": "Terms",
|
||||
"termsOfServicesTitle": "Terms",
|
||||
"support": "Support",
|
||||
"contactSupport": "Contact support",
|
||||
"help": "Help",
|
||||
"suggestFeatures": "Suggest features",
|
||||
"reportABug": "Report a bug",
|
||||
"reportBug": "Report bug",
|
||||
"social": "Social",
|
||||
"rateUsOnStore": "Rate us on {storeName}",
|
||||
"blog": "Blog",
|
||||
"merchandise": "Merchandise",
|
||||
"twitter": "Twitter",
|
||||
"mastodon": "Mastodon",
|
||||
"matrix": "Matrix",
|
||||
"discord": "Discord",
|
||||
"reddit": "Reddit"
|
||||
}
|
||||
|
||||
@@ -724,53 +724,299 @@ abstract class AppLocalizations {
|
||||
/// **'Deleting file...'**
|
||||
String get deletingFile;
|
||||
|
||||
/// No description provided for @addInformation.
|
||||
/// No description provided for @changeEmail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add information'**
|
||||
String get addInformation;
|
||||
/// **'Change email'**
|
||||
String get changeEmail;
|
||||
|
||||
/// No description provided for @addInformationDialogSubtitle.
|
||||
/// No description provided for @authToChangeYourEmail.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose the type of information you want to add'**
|
||||
String get addInformationDialogSubtitle;
|
||||
/// **'Please authenticate to change your email'**
|
||||
String get authToChangeYourEmail;
|
||||
|
||||
/// No description provided for @physicalDocument.
|
||||
/// No description provided for @changePasswordTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Physical document'**
|
||||
String get physicalDocument;
|
||||
/// **'Change password'**
|
||||
String get changePasswordTitle;
|
||||
|
||||
/// No description provided for @physicalDocumentDescription.
|
||||
/// No description provided for @authToChangeYourPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save information about documents and items in the real world.'**
|
||||
String get physicalDocumentDescription;
|
||||
/// **'Please authenticate to change your password'**
|
||||
String get authToChangeYourPassword;
|
||||
|
||||
/// No description provided for @emergencyContact.
|
||||
/// No description provided for @recoveryKey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Emergency contact'**
|
||||
String get emergencyContact;
|
||||
/// **'Recovery key'**
|
||||
String get recoveryKey;
|
||||
|
||||
/// No description provided for @emergencyContactDescription.
|
||||
/// No description provided for @ok.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save information about important contacts.'**
|
||||
String get emergencyContactDescription;
|
||||
/// **'Ok'**
|
||||
String get ok;
|
||||
|
||||
/// No description provided for @accountCredential.
|
||||
/// No description provided for @logout.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account credential'**
|
||||
String get accountCredential;
|
||||
/// **'Logout'**
|
||||
String get logout;
|
||||
|
||||
/// No description provided for @accountCredentialDescription.
|
||||
/// No description provided for @deleteAccount.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save information about your important account credentials.'**
|
||||
String get accountCredentialDescription;
|
||||
/// **'Delete account'**
|
||||
String get deleteAccount;
|
||||
|
||||
/// No description provided for @areYouSureYouWantToLogout.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to logout?'**
|
||||
String get areYouSureYouWantToLogout;
|
||||
|
||||
/// No description provided for @yesLogout.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Yes, logout'**
|
||||
String get yesLogout;
|
||||
|
||||
/// No description provided for @changePassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Change password'**
|
||||
String get changePassword;
|
||||
|
||||
/// No description provided for @authToViewYourRecoveryKey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please authenticate to view your recovery key'**
|
||||
String get authToViewYourRecoveryKey;
|
||||
|
||||
/// No description provided for @account.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Account'**
|
||||
String get account;
|
||||
|
||||
/// No description provided for @security.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Security'**
|
||||
String get security;
|
||||
|
||||
/// No description provided for @emailVerificationToggle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Email verification'**
|
||||
String get emailVerificationToggle;
|
||||
|
||||
/// No description provided for @authToChangeEmailVerificationSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please authenticate to change email verification'**
|
||||
String get authToChangeEmailVerificationSetting;
|
||||
|
||||
/// No description provided for @passkey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Passkey'**
|
||||
String get passkey;
|
||||
|
||||
/// No description provided for @authenticateGeneric.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please authenticate'**
|
||||
String get authenticateGeneric;
|
||||
|
||||
/// No description provided for @somethingWentWrong.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Something went wrong'**
|
||||
String get somethingWentWrong;
|
||||
|
||||
/// No description provided for @appLock.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'App lock'**
|
||||
String get appLock;
|
||||
|
||||
/// No description provided for @warning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Warning'**
|
||||
String get warning;
|
||||
|
||||
/// No description provided for @appLockOfflineModeWarning.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data.'**
|
||||
String get appLockOfflineModeWarning;
|
||||
|
||||
/// No description provided for @authToChangeLockscreenSetting.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please authenticate to change lockscreen setting'**
|
||||
String get authToChangeLockscreenSetting;
|
||||
|
||||
/// No description provided for @authToViewPasskey.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Please authenticate to view passkey'**
|
||||
String get authToViewPasskey;
|
||||
|
||||
/// No description provided for @theme.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Theme'**
|
||||
String get theme;
|
||||
|
||||
/// No description provided for @lightTheme.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Light'**
|
||||
String get lightTheme;
|
||||
|
||||
/// No description provided for @darkTheme.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Dark'**
|
||||
String get darkTheme;
|
||||
|
||||
/// No description provided for @systemTheme.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'System'**
|
||||
String get systemTheme;
|
||||
|
||||
/// No description provided for @settings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Settings'**
|
||||
String get settings;
|
||||
|
||||
/// No description provided for @about.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'About'**
|
||||
String get about;
|
||||
|
||||
/// No description provided for @weAreOpenSource.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'We are open source!'**
|
||||
String get weAreOpenSource;
|
||||
|
||||
/// No description provided for @privacy.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Privacy'**
|
||||
String get privacy;
|
||||
|
||||
/// No description provided for @terms.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Terms'**
|
||||
String get terms;
|
||||
|
||||
/// No description provided for @termsOfServicesTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Terms'**
|
||||
String get termsOfServicesTitle;
|
||||
|
||||
/// No description provided for @support.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Support'**
|
||||
String get support;
|
||||
|
||||
/// No description provided for @contactSupport.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact support'**
|
||||
String get contactSupport;
|
||||
|
||||
/// No description provided for @help.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Help'**
|
||||
String get help;
|
||||
|
||||
/// No description provided for @suggestFeatures.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Suggest features'**
|
||||
String get suggestFeatures;
|
||||
|
||||
/// No description provided for @reportABug.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Report a bug'**
|
||||
String get reportABug;
|
||||
|
||||
/// No description provided for @reportBug.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Report bug'**
|
||||
String get reportBug;
|
||||
|
||||
/// No description provided for @social.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Social'**
|
||||
String get social;
|
||||
|
||||
/// No description provided for @rateUsOnStore.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Rate us on {storeName}'**
|
||||
String rateUsOnStore(Object storeName);
|
||||
|
||||
/// No description provided for @blog.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Blog'**
|
||||
String get blog;
|
||||
|
||||
/// No description provided for @merchandise.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Merchandise'**
|
||||
String get merchandise;
|
||||
|
||||
/// No description provided for @twitter.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Twitter'**
|
||||
String get twitter;
|
||||
|
||||
/// No description provided for @mastodon.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Mastodon'**
|
||||
String get mastodon;
|
||||
|
||||
/// No description provided for @matrix.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Matrix'**
|
||||
String get matrix;
|
||||
|
||||
/// No description provided for @discord.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Discord'**
|
||||
String get discord;
|
||||
|
||||
/// No description provided for @reddit.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Reddit'**
|
||||
String get reddit;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -381,30 +381,157 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get deletingFile => 'Deleting file...';
|
||||
|
||||
@override
|
||||
String get addInformation => 'Add information';
|
||||
String get changeEmail => 'Change email';
|
||||
|
||||
@override
|
||||
String get addInformationDialogSubtitle =>
|
||||
'Choose the type of information you want to add';
|
||||
String get authToChangeYourEmail =>
|
||||
'Please authenticate to change your email';
|
||||
|
||||
@override
|
||||
String get physicalDocument => 'Physical document';
|
||||
String get changePasswordTitle => 'Change password';
|
||||
|
||||
@override
|
||||
String get physicalDocumentDescription =>
|
||||
'Save information about documents and items in the real world.';
|
||||
String get authToChangeYourPassword =>
|
||||
'Please authenticate to change your password';
|
||||
|
||||
@override
|
||||
String get emergencyContact => 'Emergency contact';
|
||||
String get recoveryKey => 'Recovery key';
|
||||
|
||||
@override
|
||||
String get emergencyContactDescription =>
|
||||
'Save information about important contacts.';
|
||||
String get ok => 'Ok';
|
||||
|
||||
@override
|
||||
String get accountCredential => 'Account credential';
|
||||
String get logout => 'Logout';
|
||||
|
||||
@override
|
||||
String get accountCredentialDescription =>
|
||||
'Save information about your important account credentials.';
|
||||
String get deleteAccount => 'Delete account';
|
||||
|
||||
@override
|
||||
String get areYouSureYouWantToLogout => 'Are you sure you want to logout?';
|
||||
|
||||
@override
|
||||
String get yesLogout => 'Yes, logout';
|
||||
|
||||
@override
|
||||
String get changePassword => 'Change password';
|
||||
|
||||
@override
|
||||
String get authToViewYourRecoveryKey =>
|
||||
'Please authenticate to view your recovery key';
|
||||
|
||||
@override
|
||||
String get account => 'Account';
|
||||
|
||||
@override
|
||||
String get security => 'Security';
|
||||
|
||||
@override
|
||||
String get emailVerificationToggle => 'Email verification';
|
||||
|
||||
@override
|
||||
String get authToChangeEmailVerificationSetting =>
|
||||
'Please authenticate to change email verification';
|
||||
|
||||
@override
|
||||
String get passkey => 'Passkey';
|
||||
|
||||
@override
|
||||
String get authenticateGeneric => 'Please authenticate';
|
||||
|
||||
@override
|
||||
String get somethingWentWrong => 'Something went wrong';
|
||||
|
||||
@override
|
||||
String get appLock => 'App lock';
|
||||
|
||||
@override
|
||||
String get warning => 'Warning';
|
||||
|
||||
@override
|
||||
String get appLockOfflineModeWarning =>
|
||||
'You have chosen to proceed without backups. If you forget your applock, you will be locked out from accessing your data.';
|
||||
|
||||
@override
|
||||
String get authToChangeLockscreenSetting =>
|
||||
'Please authenticate to change lockscreen setting';
|
||||
|
||||
@override
|
||||
String get authToViewPasskey => 'Please authenticate to view passkey';
|
||||
|
||||
@override
|
||||
String get theme => 'Theme';
|
||||
|
||||
@override
|
||||
String get lightTheme => 'Light';
|
||||
|
||||
@override
|
||||
String get darkTheme => 'Dark';
|
||||
|
||||
@override
|
||||
String get systemTheme => 'System';
|
||||
|
||||
@override
|
||||
String get settings => 'Settings';
|
||||
|
||||
@override
|
||||
String get about => 'About';
|
||||
|
||||
@override
|
||||
String get weAreOpenSource => 'We are open source!';
|
||||
|
||||
@override
|
||||
String get privacy => 'Privacy';
|
||||
|
||||
@override
|
||||
String get terms => 'Terms';
|
||||
|
||||
@override
|
||||
String get termsOfServicesTitle => 'Terms';
|
||||
|
||||
@override
|
||||
String get support => 'Support';
|
||||
|
||||
@override
|
||||
String get contactSupport => 'Contact support';
|
||||
|
||||
@override
|
||||
String get help => 'Help';
|
||||
|
||||
@override
|
||||
String get suggestFeatures => 'Suggest features';
|
||||
|
||||
@override
|
||||
String get reportABug => 'Report a bug';
|
||||
|
||||
@override
|
||||
String get reportBug => 'Report bug';
|
||||
|
||||
@override
|
||||
String get social => 'Social';
|
||||
|
||||
@override
|
||||
String rateUsOnStore(Object storeName) {
|
||||
return 'Rate us on $storeName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get blog => 'Blog';
|
||||
|
||||
@override
|
||||
String get merchandise => 'Merchandise';
|
||||
|
||||
@override
|
||||
String get twitter => 'Twitter';
|
||||
|
||||
@override
|
||||
String get mastodon => 'Mastodon';
|
||||
|
||||
@override
|
||||
String get matrix => 'Matrix';
|
||||
|
||||
@override
|
||||
String get discord => 'Discord';
|
||||
|
||||
@override
|
||||
String get reddit => 'Reddit';
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:ente_lock_screen/ui/app_lock.dart';
|
||||
import 'package:ente_lock_screen/ui/lock_screen.dart';
|
||||
import 'package:ente_logging/logging.dart';
|
||||
import 'package:ente_network/network.dart';
|
||||
import "package:ente_strings/l10n/strings_localizations.dart";
|
||||
import "package:ente_ui/theme/theme_config.dart";
|
||||
import 'package:ente_ui/utils/window_listener_service.dart';
|
||||
import 'package:ente_utils/platform_util.dart';
|
||||
import "package:flutter/material.dart";
|
||||
@@ -84,6 +86,7 @@ Future<void> _initSystemTray() async {
|
||||
}
|
||||
|
||||
Future<void> _runInForeground() async {
|
||||
AppThemeConfig.initialize(EnteApp.locker);
|
||||
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
|
||||
return await _runWithLogs(() async {
|
||||
_logger.info("Starting app in foreground");
|
||||
@@ -102,7 +105,10 @@ Future<void> _runInForeground() async {
|
||||
locale: locale,
|
||||
savedThemeMode: savedThemeMode,
|
||||
supportedLocales: appSupportedLocales,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
localizationsDelegates: const [
|
||||
...StringsLocalizations.localizationsDelegates,
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
],
|
||||
localeListResolutionCallback: localResolutionCallBack,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme_data.dart";
|
||||
import "package:expandable/expandable.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
|
||||
class ExpandableMenuItemWidget extends StatefulWidget {
|
||||
final String title;
|
||||
final Widget selectionOptionsWidget;
|
||||
final IconData leadingIcon;
|
||||
const ExpandableMenuItemWidget({
|
||||
required this.title,
|
||||
required this.selectionOptionsWidget,
|
||||
required this.leadingIcon,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ExpandableMenuItemWidget> createState() =>
|
||||
_ExpandableMenuItemWidgetState();
|
||||
}
|
||||
|
||||
class _ExpandableMenuItemWidgetState extends State<ExpandableMenuItemWidget> {
|
||||
final expandableController = ExpandableController(initialExpanded: false);
|
||||
@override
|
||||
void initState() {
|
||||
expandableController.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
expandableController.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final enteColorScheme = Theme.of(context).colorScheme.enteTheme.colorScheme;
|
||||
final backgroundColor =
|
||||
MediaQuery.of(context).platformBrightness == Brightness.light
|
||||
? enteColorScheme.backgroundElevated2
|
||||
: enteColorScheme.backgroundElevated;
|
||||
return AnimatedContainer(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
color: expandableController.value ? backgroundColor : null,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: ExpandableNotifier(
|
||||
controller: expandableController,
|
||||
child: ScrollOnExpand(
|
||||
child: ExpandablePanel(
|
||||
header: MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: widget.title,
|
||||
makeTextBold: true,
|
||||
),
|
||||
isExpandable: true,
|
||||
leadingIcon: widget.leadingIcon,
|
||||
trailingIcon: Icons.expand_more,
|
||||
menuItemColor: enteColorScheme.fillFaint,
|
||||
expandableController: expandableController,
|
||||
),
|
||||
collapsed: const SizedBox.shrink(),
|
||||
expanded: widget.selectionOptionsWidget,
|
||||
theme: getExpandableTheme(),
|
||||
controller: expandableController,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
import 'package:ente_ui/components/buttons/button_widget.dart';
|
||||
import 'package:ente_ui/components/buttons/models/button_type.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:locker/l10n/l10n.dart';
|
||||
|
||||
enum InformationType {
|
||||
physicalDocument,
|
||||
emergencyContact,
|
||||
accountCredential,
|
||||
}
|
||||
|
||||
class InformationAdditionResult {
|
||||
final InformationType type;
|
||||
|
||||
InformationAdditionResult({
|
||||
required this.type,
|
||||
});
|
||||
}
|
||||
|
||||
class InformationAdditionDialog extends StatefulWidget {
|
||||
const InformationAdditionDialog({super.key});
|
||||
|
||||
@override
|
||||
State<InformationAdditionDialog> createState() =>
|
||||
_InformationAdditionDialogState();
|
||||
}
|
||||
|
||||
class _InformationAdditionDialogState extends State<InformationAdditionDialog> {
|
||||
void _onTypeSelected(InformationType type) {
|
||||
final result = InformationAdditionResult(type: type);
|
||||
Navigator.of(context).pop(result);
|
||||
}
|
||||
|
||||
Future<void> _onCancel() async {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final textTheme = getEnteTextTheme(context);
|
||||
|
||||
return Dialog(
|
||||
backgroundColor: colorScheme.backgroundElevated,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Container(
|
||||
width: 400,
|
||||
constraints: const BoxConstraints(maxHeight: 600),
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.post_add,
|
||||
color: Colors.blue,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.l10n.addInformation,
|
||||
style: textTheme.largeBold,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.addInformationDialogSubtitle,
|
||||
style: textTheme.body.copyWith(
|
||||
color: colorScheme.textMuted,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildOptionTile(
|
||||
type: InformationType.physicalDocument,
|
||||
icon: Icons.description,
|
||||
title: context.l10n.physicalDocument,
|
||||
subtitle: context.l10n.physicalDocumentDescription,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: textTheme,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildOptionTile(
|
||||
type: InformationType.emergencyContact,
|
||||
icon: Icons.emergency,
|
||||
title: context.l10n.emergencyContact,
|
||||
subtitle: context.l10n.emergencyContactDescription,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: textTheme,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildOptionTile(
|
||||
type: InformationType.accountCredential,
|
||||
icon: Icons.key,
|
||||
title: context.l10n.accountCredential,
|
||||
subtitle: context.l10n.accountCredentialDescription,
|
||||
colorScheme: colorScheme,
|
||||
textTheme: textTheme,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Flexible(
|
||||
child: ButtonWidget(
|
||||
buttonType: ButtonType.secondary,
|
||||
labelText: context.l10n.cancel,
|
||||
onTap: _onCancel,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildOptionTile({
|
||||
required InformationType type,
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required colorScheme,
|
||||
required textTheme,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: () => _onTypeSelected(type),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.fillFaint,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: colorScheme.strokeFaint,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.fillMuted,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: colorScheme.textMuted,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: textTheme.body.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: colorScheme.textBase,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: textTheme.small.copyWith(
|
||||
color: colorScheme.textMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<InformationAdditionResult?> showInformationAdditionDialog(
|
||||
BuildContext context,
|
||||
) async {
|
||||
return showDialog<InformationAdditionResult>(
|
||||
context: context,
|
||||
barrierColor: getEnteColorScheme(context).backdropBase,
|
||||
builder: (context) => const InformationAdditionDialog(),
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import "package:ente_accounts/services/user_service.dart";
|
||||
import 'package:ente_events/event_bus.dart';
|
||||
import 'package:ente_ui/components/buttons/gradient_button.dart';
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
@@ -14,12 +15,12 @@ import 'package:locker/l10n/l10n.dart';
|
||||
import 'package:locker/services/collections/collections_service.dart';
|
||||
import 'package:locker/services/collections/models/collection.dart';
|
||||
import 'package:locker/services/files/sync/models/file.dart';
|
||||
import 'package:locker/ui/components/information_addition_dialog.dart';
|
||||
import 'package:locker/ui/components/recents_section_widget.dart';
|
||||
import 'package:locker/ui/components/search_result_view.dart';
|
||||
import 'package:locker/ui/mixins/search_mixin.dart';
|
||||
import 'package:locker/ui/pages/all_collections_page.dart';
|
||||
import 'package:locker/ui/pages/collection_page.dart';
|
||||
import "package:locker/ui/pages/settings_page.dart";
|
||||
import 'package:locker/ui/pages/uploader_page.dart';
|
||||
import 'package:locker/utils/collection_actions.dart';
|
||||
import 'package:locker/utils/collection_sort_util.dart';
|
||||
@@ -37,12 +38,19 @@ class HomePage extends UploaderPage {
|
||||
|
||||
class _HomePageState extends UploaderPageState<HomePage>
|
||||
with TickerProviderStateMixin, SearchMixin {
|
||||
late final _settingsPage = SettingsPage(
|
||||
emailNotifier: UserService.instance.emailValueNotifier,
|
||||
scaffoldKey: scaffoldKey,
|
||||
);
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
bool _isLoading = true;
|
||||
bool _isSettingsOpen = false;
|
||||
|
||||
List<Collection> _collections = [];
|
||||
List<Collection> _filteredCollections = [];
|
||||
List<EnteFile> _recentFiles = [];
|
||||
List<EnteFile> _filteredFiles = [];
|
||||
Map<int, int> _collectionFileCounts = {};
|
||||
bool _isLoading = true;
|
||||
String? _error;
|
||||
final _logger = Logger('HomePage');
|
||||
StreamSubscription? _mediaStreamSubscription;
|
||||
@@ -64,8 +72,7 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
List<EnteFile> files,
|
||||
) {
|
||||
setState(() {
|
||||
_filteredCollections =
|
||||
CollectionSortUtil.filterAndSortCollections(collections);
|
||||
_filteredCollections = _filterOutUncategorized(collections);
|
||||
_filteredFiles = files;
|
||||
});
|
||||
}
|
||||
@@ -74,8 +81,7 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
void onSearchStateChanged(bool isActive) {
|
||||
if (!isActive) {
|
||||
setState(() {
|
||||
_filteredCollections =
|
||||
CollectionSortUtil.filterAndSortCollections(_collections);
|
||||
_filteredCollections = _filterOutUncategorized(_collections);
|
||||
_filteredFiles = _recentFiles;
|
||||
});
|
||||
}
|
||||
@@ -83,6 +89,10 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
|
||||
List<Collection> get _displayedCollections {
|
||||
final collections = isSearchActive ? _filteredCollections : _collections;
|
||||
return _filterOutUncategorized(collections);
|
||||
}
|
||||
|
||||
List<Collection> _filterOutUncategorized(List<Collection> collections) {
|
||||
return CollectionSortUtil.filterAndSortCollections(collections);
|
||||
}
|
||||
|
||||
@@ -260,8 +270,7 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
|
||||
setState(() {
|
||||
_collections = sortedCollections;
|
||||
_filteredCollections =
|
||||
CollectionSortUtil.filterAndSortCollections(sortedCollections);
|
||||
_filteredCollections = _filterOutUncategorized(sortedCollections);
|
||||
_filteredFiles = _recentFiles;
|
||||
_isLoading = false;
|
||||
});
|
||||
@@ -276,18 +285,31 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
}
|
||||
|
||||
Future<void> _loadRecentFiles(List<Collection> collections) async {
|
||||
final allFiles = await CollectionService.instance.getAllFiles();
|
||||
final allFiles = <EnteFile>[];
|
||||
|
||||
final uniqueFilesMap = <String, EnteFile>{};
|
||||
allFiles.addAll(await CollectionService.instance.getAllFiles());
|
||||
|
||||
final uniqueFiles = <EnteFile>[];
|
||||
final seenHashes = <String>{};
|
||||
final seenIds = <int>{};
|
||||
|
||||
for (final file in allFiles) {
|
||||
final key = file.uploadedFileID?.toString() ?? file.toString();
|
||||
if (!uniqueFilesMap.containsKey(key)) {
|
||||
uniqueFilesMap[key] = file;
|
||||
bool isDuplicate = false;
|
||||
|
||||
if (file.hash != null && seenHashes.contains(file.hash)) {
|
||||
isDuplicate = true;
|
||||
} else if (file.uploadedFileID != null &&
|
||||
seenIds.contains(file.uploadedFileID)) {
|
||||
isDuplicate = true;
|
||||
}
|
||||
|
||||
if (!isDuplicate) {
|
||||
uniqueFiles.add(file);
|
||||
if (file.hash != null) seenHashes.add(file.hash!);
|
||||
if (file.uploadedFileID != null) seenIds.add(file.uploadedFileID!);
|
||||
}
|
||||
}
|
||||
|
||||
final uniqueFiles = uniqueFilesMap.values.toList();
|
||||
uniqueFiles.sort((a, b) {
|
||||
final timeA = a.updationTime ?? a.modificationTime ?? 0;
|
||||
final timeB = b.updationTime ?? b.modificationTime ?? 0;
|
||||
@@ -307,38 +329,55 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: handleKeyEvent,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: buildSearchLeading(),
|
||||
title: GestureDetector(
|
||||
onLongPress: () {
|
||||
sendLogs(
|
||||
context,
|
||||
'vishnu@ente.io',
|
||||
subject: 'Locker logs',
|
||||
body: 'Debug logs for Locker app.\n\n',
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
'Locker',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (_, result) async {
|
||||
if (_isSettingsOpen) {
|
||||
scaffoldKey.currentState!.closeDrawer();
|
||||
return;
|
||||
} else if (!Platform.isAndroid) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
},
|
||||
child: KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: handleKeyEvent,
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
drawer: Drawer(
|
||||
width: 428,
|
||||
child: _settingsPage,
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
actions: [
|
||||
buildSearchAction(),
|
||||
...buildSearchActions(),
|
||||
],
|
||||
drawerEnableOpenDragGesture: !Platform.isAndroid,
|
||||
onDrawerChanged: (isOpened) => _isSettingsOpen = isOpened,
|
||||
appBar: AppBar(
|
||||
leading: buildSearchLeading(),
|
||||
title: GestureDetector(
|
||||
onLongPress: () {
|
||||
sendLogs(
|
||||
context,
|
||||
'vishnu@ente.io',
|
||||
subject: 'Locker logs',
|
||||
body: 'Debug logs for Locker app.\n\n',
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
'Locker',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
foregroundColor: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
actions: [
|
||||
buildSearchAction(),
|
||||
...buildSearchActions(),
|
||||
],
|
||||
),
|
||||
body: _buildBody(),
|
||||
floatingActionButton:
|
||||
isSearchActive ? const SizedBox.shrink() : _buildMultiOptionFab(),
|
||||
),
|
||||
body: _buildBody(),
|
||||
floatingActionButton:
|
||||
isSearchActive ? const SizedBox.shrink() : _buildMultiOptionFab(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -400,18 +439,40 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).size.height - 200,
|
||||
child: _buildEmptyState(
|
||||
icon: Icons.folder_outlined,
|
||||
title: context.l10n.noCollectionsFound,
|
||||
subtitle: context.l10n.createYourFirstCollection,
|
||||
action: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: GradientButton(
|
||||
onTap: _createCollection,
|
||||
text: context.l10n.createCollection,
|
||||
iconData: Icons.add,
|
||||
paddingValue: 8.0,
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.folder_outlined,
|
||||
size: 64,
|
||||
color: Colors.grey,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.noCollectionsFound,
|
||||
style: getEnteTextTheme(context).large.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.createYourFirstCollection,
|
||||
style: getEnteTextTheme(context).body.copyWith(
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40),
|
||||
child: GradientButton(
|
||||
onTap: _createCollection,
|
||||
text: context.l10n.createCollection,
|
||||
iconData: Icons.add,
|
||||
paddingValue: 8.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -447,21 +508,42 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
if (_recentFiles.isEmpty) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 40),
|
||||
child: _buildEmptyState(
|
||||
icon: Icons.description_outlined,
|
||||
title: context.l10n.nothingYet,
|
||||
subtitle: context.l10n.uploadYourFirstDocument,
|
||||
action: GradientButton(
|
||||
onTap: addFile,
|
||||
text: context.l10n.uploadDocument,
|
||||
iconData: Icons.file_upload,
|
||||
paddingValue: 8.0,
|
||||
child: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.description_outlined,
|
||||
size: 48,
|
||||
color: Colors.grey[400],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.nothingYet,
|
||||
style: getEnteTextTheme(context).body.copyWith(
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.uploadYourFirstDocument,
|
||||
style: getEnteTextTheme(context).small.copyWith(
|
||||
color: Colors.grey[500],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
GradientButton(
|
||||
onTap: addFile,
|
||||
text: context.l10n.uploadDocument,
|
||||
iconData: Icons.file_upload,
|
||||
paddingValue: 8.0,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return RecentsSectionWidget(
|
||||
collections: CollectionSortUtil.filterAndSortCollections(_collections),
|
||||
collections: _filterOutUncategorized(_collections),
|
||||
recentFiles: _recentFiles,
|
||||
);
|
||||
}
|
||||
@@ -475,113 +557,6 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addInformation() async {
|
||||
final result = await showInformationAdditionDialog(context);
|
||||
|
||||
if (result != null && mounted) {
|
||||
switch (result.type) {
|
||||
case InformationType.physicalDocument:
|
||||
await _addPhysicalDocument();
|
||||
break;
|
||||
case InformationType.emergencyContact:
|
||||
await _addEmergencyContact();
|
||||
break;
|
||||
case InformationType.accountCredential:
|
||||
await _addAccountCredential();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addPhysicalDocument() async {
|
||||
SnackBarUtils.showInfoSnackBar(
|
||||
context,
|
||||
"Soon",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addEmergencyContact() async {
|
||||
SnackBarUtils.showInfoSnackBar(
|
||||
context,
|
||||
"Soon",
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addAccountCredential() async {
|
||||
SnackBarUtils.showInfoSnackBar(
|
||||
context,
|
||||
"Soon",
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyState({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String subtitle,
|
||||
Widget? action,
|
||||
}) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 64, color: Colors.grey),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
title,
|
||||
style: getEnteTextTheme(context).large.copyWith(color: Colors.grey),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
subtitle,
|
||||
style: getEnteTextTheme(context).body.copyWith(color: Colors.grey),
|
||||
),
|
||||
if (action != null) ...[
|
||||
const SizedBox(height: 24),
|
||||
action,
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFabLabel(String text) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: getEnteColorScheme(context).fillBase,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
text,
|
||||
style: getEnteTextTheme(context).small.copyWith(
|
||||
color: getEnteColorScheme(context).backgroundBase,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFabOption({
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required VoidCallback onPressed,
|
||||
required String heroTag,
|
||||
}) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildFabLabel(label),
|
||||
const SizedBox(width: 8),
|
||||
FloatingActionButton(
|
||||
heroTag: heroTag,
|
||||
mini: true,
|
||||
onPressed: onPressed,
|
||||
backgroundColor: getEnteColorScheme(context).fillBase,
|
||||
child: Icon(icon),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCollectionsHeader() {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
@@ -708,14 +683,39 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
scale: _animation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
child: _buildFabOption(
|
||||
label: context.l10n.addInformation,
|
||||
icon: Icons.post_add,
|
||||
onPressed: () {
|
||||
_toggleFab();
|
||||
_addInformation();
|
||||
},
|
||||
heroTag: "addInformation",
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getEnteColorScheme(context).fillBase,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.createCollectionTooltip,
|
||||
style: getEnteTextTheme(context).small.copyWith(
|
||||
color: getEnteColorScheme(context)
|
||||
.backgroundBase,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FloatingActionButton(
|
||||
heroTag: "createCollection",
|
||||
mini: true,
|
||||
onPressed: () {
|
||||
_toggleFab();
|
||||
_createCollection();
|
||||
},
|
||||
backgroundColor:
|
||||
getEnteColorScheme(context).fillBase,
|
||||
child: const Icon(Icons.create_new_folder),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -724,14 +724,40 @@ class _HomePageState extends UploaderPageState<HomePage>
|
||||
scale: _animation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: _buildFabOption(
|
||||
label: context.l10n.uploadDocumentTooltip,
|
||||
icon: Icons.file_upload,
|
||||
onPressed: () {
|
||||
_toggleFab();
|
||||
addFile();
|
||||
},
|
||||
heroTag: "addFile",
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getEnteColorScheme(context).fillBase,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: Text(
|
||||
context.l10n.uploadDocumentTooltip,
|
||||
style:
|
||||
getEnteTextTheme(context).small.copyWith(
|
||||
color: getEnteColorScheme(context)
|
||||
.backgroundBase,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
FloatingActionButton(
|
||||
heroTag: "addFile",
|
||||
mini: true,
|
||||
onPressed: () {
|
||||
_toggleFab();
|
||||
addFile();
|
||||
},
|
||||
backgroundColor:
|
||||
getEnteColorScheme(context).fillBase,
|
||||
child: const Icon(Icons.file_upload),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:ente_ui/components/buttons/gradient_button.dart';
|
||||
import 'package:ente_ui/components/developer_settings_widget.dart';
|
||||
import "package:ente_ui/pages/developer_settings_page.dart";
|
||||
import 'package:ente_ui/theme/ente_theme.dart';
|
||||
import "package:ente_ui/theme/ente_theme_data.dart";
|
||||
import 'package:ente_ui/utils/dialog_util.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:locker/l10n/l10n.dart';
|
||||
@@ -103,12 +104,15 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.copyWith(
|
||||
color: Colors.white38,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBoardingBodyColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
@@ -125,19 +129,14 @@ class _OnboardingPageState extends State<OnboardingPage> {
|
||||
child: Hero(
|
||||
tag: "log_in",
|
||||
child: ElevatedButton(
|
||||
style: ElevatedButton.styleFrom().copyWith(
|
||||
shape: WidgetStateProperty.all<
|
||||
RoundedRectangleBorder>(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(32.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.colorScheme
|
||||
.optionalActionButtonStyle,
|
||||
onPressed: _navigateToSignInPage,
|
||||
child: Text(
|
||||
l10n.existingUser,
|
||||
style: const TextStyle(
|
||||
color: Colors.white, // same for both themes
|
||||
color: Colors.black, // same for both themes
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
125
mobile/apps/locker/lib/ui/pages/settings_page.dart
Normal file
@@ -0,0 +1,125 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:ente_accounts/services/user_service.dart";
|
||||
import "package:ente_ui/theme/colors.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:locker/services/configuration.dart";
|
||||
import "package:locker/ui/settings/about_section_widget.dart";
|
||||
import 'package:locker/ui/settings/account_section_widget.dart';
|
||||
import "package:locker/ui/settings/app_version_widget.dart";
|
||||
import "package:locker/ui/settings/security_section_widget.dart";
|
||||
import "package:locker/ui/settings/social_section_widget.dart";
|
||||
import "package:locker/ui/settings/support_section_widget.dart";
|
||||
import "package:locker/ui/settings/theme_switch_widget.dart";
|
||||
import "package:locker/ui/settings/title_bar_widget.dart";
|
||||
|
||||
class SettingsPage extends StatelessWidget {
|
||||
final ValueNotifier<String?> emailNotifier;
|
||||
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||
|
||||
const SettingsPage({
|
||||
super.key,
|
||||
required this.emailNotifier,
|
||||
required this.scaffoldKey,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
|
||||
if (hasLoggedIn) {
|
||||
UserService.instance.getUserDetailsV2().ignore();
|
||||
}
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
color: enteColorScheme.backdropBase,
|
||||
child: _getBody(context, enteColorScheme),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getBody(BuildContext context, EnteColorScheme colorScheme) {
|
||||
final hasLoggedIn = Configuration.instance.hasConfiguredAccount();
|
||||
final enteTextTheme = getEnteTextTheme(context);
|
||||
const sectionSpacing = SizedBox(height: 8);
|
||||
final List<Widget> contents = [];
|
||||
|
||||
if (hasLoggedIn) {
|
||||
contents.add(
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: AnimatedBuilder(
|
||||
// [AnimatedBuilder] accepts any [Listenable] subtype.
|
||||
animation: emailNotifier,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return Text(
|
||||
emailNotifier.value!,
|
||||
style: enteTextTheme.body.copyWith(
|
||||
color: colorScheme.textMuted,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
contents.addAll([
|
||||
const SizedBox(height: 12),
|
||||
const AccountSectionWidget(),
|
||||
sectionSpacing,
|
||||
]);
|
||||
|
||||
contents.addAll([
|
||||
const SecuritySectionWidget(),
|
||||
sectionSpacing,
|
||||
]);
|
||||
|
||||
if (Platform.isAndroid ||
|
||||
Platform.isWindows ||
|
||||
Platform.isLinux ||
|
||||
kDebugMode) {
|
||||
contents.addAll([
|
||||
const ThemeSwitchWidget(),
|
||||
sectionSpacing,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
contents.addAll([
|
||||
const SupportSectionWidget(),
|
||||
sectionSpacing,
|
||||
const SocialSectionWidget(),
|
||||
sectionSpacing,
|
||||
const AboutSectionWidget(),
|
||||
const AppVersionWidget(),
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(bottom: 60),
|
||||
),
|
||||
]);
|
||||
|
||||
return SafeArea(
|
||||
bottom: false,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SettingsTitleBarWidget(
|
||||
scaffoldKey: scaffoldKey,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 24),
|
||||
child: Column(
|
||||
children: contents,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
84
mobile/apps/locker/lib/ui/settings/about_section_widget.dart
Normal file
@@ -0,0 +1,84 @@
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_utils/platform_util.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
import "package:url_launcher/url_launcher.dart";
|
||||
|
||||
class AboutSectionWidget extends StatelessWidget {
|
||||
const AboutSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: context.l10n.about,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.info_outline,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.weAreOpenSource,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrl(Uri.parse("https://github.com/ente-io/ente"));
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
AboutMenuItemWidget(
|
||||
title: context.l10n.privacy,
|
||||
url: "https://ente.io/privacy",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
AboutMenuItemWidget(
|
||||
title: context.l10n.termsOfServicesTitle,
|
||||
url: "https://ente.io/terms",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class AboutMenuItemWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final String url;
|
||||
final String? webPageTitle;
|
||||
const AboutMenuItemWidget({
|
||||
required this.title,
|
||||
required this.url,
|
||||
this.webPageTitle,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: title,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await PlatformUtil.openWebView(
|
||||
context,
|
||||
webPageTitle ?? title,
|
||||
url,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
181
mobile/apps/locker/lib/ui/settings/account_section_widget.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
import "package:ente_accounts/pages/change_email_dialog.dart";
|
||||
import "package:ente_accounts/pages/delete_account_page.dart";
|
||||
import "package:ente_accounts/pages/password_entry_page.dart";
|
||||
import "package:ente_accounts/pages/recovery_key_page.dart";
|
||||
import "package:ente_accounts/services/user_service.dart";
|
||||
import "package:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import "package:ente_lock_screen/local_authentication_service.dart";
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_ui/utils/dialog_util.dart";
|
||||
import "package:ente_utils/navigation_util.dart";
|
||||
import "package:ente_utils/platform_util.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/services/configuration.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/pages/home_page.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
|
||||
class AccountSectionWidget extends StatelessWidget {
|
||||
const AccountSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return ExpandableMenuItemWidget(
|
||||
title: l10n.account,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.account_circle_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Column _getSectionOptions(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final List<Widget> children = [];
|
||||
children.addAll([
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.changeEmail,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToChangeYourEmail,
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (hasAuthenticated) {
|
||||
// ignore: unawaited_futures
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const ChangeEmailDialog();
|
||||
},
|
||||
barrierColor: Colors.black.withValues(alpha: 0.85),
|
||||
barrierDismissible: false,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
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) {
|
||||
// ignore: unawaited_futures
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return PasswordEntryPage(
|
||||
Configuration.instance,
|
||||
PasswordEntryMode.update,
|
||||
const HomePage(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.recoveryKey,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToViewYourRecoveryKey,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
String recoveryKey;
|
||||
try {
|
||||
recoveryKey =
|
||||
CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey());
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(
|
||||
context,
|
||||
RecoveryKeyPage(
|
||||
Configuration.instance,
|
||||
recoveryKey,
|
||||
l10n.ok,
|
||||
showAppBar: true,
|
||||
onDone: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.logout,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
_onLogoutTapped(context);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.deleteAccount,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final config = Configuration.instance;
|
||||
// ignore: unawaited_futures
|
||||
routeToPage(context, DeleteAccountPage(config));
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
return Column(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
void _onLogoutTapped(BuildContext context) {
|
||||
showChoiceActionSheet(
|
||||
context,
|
||||
title: context.l10n.areYouSureYouWantToLogout,
|
||||
firstButtonLabel: context.l10n.yesLogout,
|
||||
isCritical: true,
|
||||
firstButtonOnTap: () async {
|
||||
await UserService.instance.logout(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
66
mobile/apps/locker/lib/ui/settings/app_version_widget.dart
Normal file
@@ -0,0 +1,66 @@
|
||||
import "package:ente_ui/utils/dialog_util.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class AppVersionWidget extends StatefulWidget {
|
||||
const AppVersionWidget({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AppVersionWidget> createState() => _AppVersionWidgetState();
|
||||
}
|
||||
|
||||
class _AppVersionWidgetState extends State<AppVersionWidget> {
|
||||
static const kTapThresholdForInspector = 5;
|
||||
static const kConsecutiveTapTimeWindowInMilliseconds = 2000;
|
||||
static const kDummyDelayDurationInMilliseconds = 1500;
|
||||
|
||||
int? _lastTap;
|
||||
int _consecutiveTaps = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
final int now = DateTime.now().millisecondsSinceEpoch;
|
||||
if (now - (_lastTap ?? now) < kConsecutiveTapTimeWindowInMilliseconds) {
|
||||
_consecutiveTaps++;
|
||||
if (_consecutiveTaps == kTapThresholdForInspector) {
|
||||
final dialog =
|
||||
createProgressDialog(context, "Starting network inspector...");
|
||||
await dialog.show();
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: kDummyDelayDurationInMilliseconds),
|
||||
);
|
||||
await dialog.hide();
|
||||
}
|
||||
} else {
|
||||
_consecutiveTaps = 1;
|
||||
}
|
||||
_lastTap = now;
|
||||
},
|
||||
child: FutureBuilder<String>(
|
||||
future: _getAppVersion(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Text(
|
||||
"Version: ${snapshot.data!}",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<String> _getAppVersion() async {
|
||||
final pkgInfo = await PackageInfo.fromPlatform();
|
||||
return pkgInfo.version;
|
||||
}
|
||||
}
|
||||
14
mobile/apps/locker/lib/ui/settings/common_settings.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import "package:expandable/expandable.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
Widget sectionOptionSpacing = const SizedBox(height: 6);
|
||||
|
||||
ExpandableThemeData getExpandableTheme() {
|
||||
return const ExpandableThemeData(
|
||||
hasIcon: false,
|
||||
useInkWell: false,
|
||||
tapBodyToCollapse: true,
|
||||
tapBodyToExpand: true,
|
||||
animationDuration: Duration(milliseconds: 400),
|
||||
);
|
||||
}
|
||||
217
mobile/apps/locker/lib/ui/settings/security_section_widget.dart
Normal file
@@ -0,0 +1,217 @@
|
||||
import "dart:async";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:ente_accounts/models/user_details.dart";
|
||||
import "package:ente_accounts/pages/request_pwd_verification_page.dart";
|
||||
import "package:ente_accounts/services/passkey_service.dart";
|
||||
import "package:ente_accounts/services/user_service.dart";
|
||||
import "package:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import "package:ente_lock_screen/auth_util.dart";
|
||||
import "package:ente_lock_screen/local_authentication_service.dart";
|
||||
import "package:ente_lock_screen/lock_screen_settings.dart";
|
||||
import "package:ente_lock_screen/ui/lock_screen_options.dart";
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/components/toggle_switch_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_ui/utils/dialog_util.dart";
|
||||
import "package:ente_ui/utils/toast_util.dart";
|
||||
import "package:ente_utils/navigation_util.dart";
|
||||
import "package:ente_utils/platform_util.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/services/configuration.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
import "package:logging/logging.dart";
|
||||
|
||||
class SecuritySectionWidget extends StatefulWidget {
|
||||
const SecuritySectionWidget({super.key});
|
||||
|
||||
@override
|
||||
State<SecuritySectionWidget> createState() => _SecuritySectionWidgetState();
|
||||
}
|
||||
|
||||
class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
final _config = Configuration.instance;
|
||||
late bool _hasLoggedIn;
|
||||
final Logger _logger = Logger('SecuritySectionWidget');
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_hasLoggedIn = _config.hasConfiguredAccount();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return ExpandableMenuItemWidget(
|
||||
title: l10n.security,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.local_police_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final List<Widget> children = [];
|
||||
if (_hasLoggedIn) {
|
||||
children.addAll(
|
||||
[
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: l10n.emailVerificationToggle,
|
||||
),
|
||||
trailingWidget: ToggleSwitchWidget(
|
||||
value: () => UserService.instance.hasEmailMFAEnabled(),
|
||||
onChanged: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService
|
||||
.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToChangeEmailVerificationSetting,
|
||||
);
|
||||
final isEmailMFAEnabled =
|
||||
UserService.instance.hasEmailMFAEnabled();
|
||||
if (hasAuthenticated) {
|
||||
await updateEmailMFA(!isEmailMFAEnabled);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.passkey,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
final hasAuthenticated = await LocalAuthenticationService.instance
|
||||
.requestLocalAuthentication(
|
||||
context,
|
||||
l10n.authToViewPasskey,
|
||||
);
|
||||
if (hasAuthenticated) {
|
||||
await onPasskeyClick(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
children.add(sectionOptionSpacing);
|
||||
}
|
||||
children.addAll([
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.appLock,
|
||||
),
|
||||
surfaceExecutionStates: false,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
if (await LockScreenSettings.instance.shouldShowLockScreen()) {
|
||||
final bool result = await requestAuthentication(
|
||||
context,
|
||||
context.l10n.authToChangeLockscreenSetting,
|
||||
);
|
||||
if (result) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const LockScreenOptions();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const LockScreenOptions();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
]);
|
||||
|
||||
return Column(
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> onPasskeyClick(BuildContext buildContext) async {
|
||||
try {
|
||||
final hasAuthenticated =
|
||||
await LocalAuthenticationService.instance.requestLocalAuthentication(
|
||||
context,
|
||||
context.l10n.authenticateGeneric,
|
||||
);
|
||||
await PlatformUtil.refocusWindows();
|
||||
if (!hasAuthenticated) {
|
||||
return;
|
||||
}
|
||||
final isPassKeyResetEnabled =
|
||||
await PasskeyService.instance.isPasskeyRecoveryEnabled();
|
||||
if (!isPassKeyResetEnabled) {
|
||||
final Uint8List recoveryKey = Configuration.instance.getRecoveryKey();
|
||||
final resetKey = CryptoUtil.generateKey();
|
||||
final resetKeyBase64 = CryptoUtil.bin2base64(resetKey);
|
||||
final encryptionResult = CryptoUtil.encryptSync(
|
||||
resetKey,
|
||||
recoveryKey,
|
||||
);
|
||||
await PasskeyService.instance.configurePasskeyRecovery(
|
||||
resetKeyBase64,
|
||||
CryptoUtil.bin2base64(encryptionResult.encryptedData!),
|
||||
CryptoUtil.bin2base64(encryptionResult.nonce!),
|
||||
);
|
||||
}
|
||||
PasskeyService.instance.openPasskeyPage(buildContext).ignore();
|
||||
} catch (e, s) {
|
||||
_logger.severe("failed to open passkey page", e, s);
|
||||
await showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateEmailMFA(bool isEnabled) async {
|
||||
try {
|
||||
final UserDetails details =
|
||||
await UserService.instance.getUserDetailsV2(memoryCount: false);
|
||||
if ((details.profileData?.canDisableEmailMFA ?? false) == false) {
|
||||
await routeToPage(
|
||||
context,
|
||||
RequestPasswordVerificationPage(
|
||||
Configuration.instance,
|
||||
onPasswordVerified: (Uint8List keyEncryptionKey) async {
|
||||
final Uint8List loginKey =
|
||||
await CryptoUtil.deriveLoginKey(keyEncryptionKey);
|
||||
await UserService.instance.registerOrUpdateSrp(loginKey);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
await UserService.instance.updateEmailMFA(isEnabled);
|
||||
} catch (e) {
|
||||
showToast(context, context.l10n.somethingWentWrong);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import 'dart:io';
|
||||
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
class SocialSectionWidget extends StatelessWidget {
|
||||
const SocialSectionWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return ExpandableMenuItemWidget(
|
||||
title: l10n.social,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.interests_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
|
||||
final List<Widget> options = [
|
||||
sectionOptionSpacing,
|
||||
SocialsMenuItemWidget(
|
||||
l10n.blog,
|
||||
"https://ente.io/blog",
|
||||
launchInExternalApp: !Platform.isAndroid,
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
SocialsMenuItemWidget(
|
||||
l10n.merchandise,
|
||||
"https://shop.ente.io",
|
||||
launchInExternalApp: !Platform.isAndroid,
|
||||
),
|
||||
const SocialsMenuItemWidget("Twitter", "https://twitter.com/enteio"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Mastodon", "https://fosstodon.org/@ente"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Matrix", "https://ente.io/matrix"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Discord", "https://ente.io/discord"),
|
||||
sectionOptionSpacing,
|
||||
const SocialsMenuItemWidget("Reddit", "https://reddit.com/r/enteio"),
|
||||
sectionOptionSpacing,
|
||||
];
|
||||
return Column(children: options);
|
||||
}
|
||||
}
|
||||
|
||||
class SocialsMenuItemWidget extends StatelessWidget {
|
||||
final String text;
|
||||
final String url;
|
||||
final bool launchInExternalApp;
|
||||
|
||||
const SocialsMenuItemWidget(
|
||||
this.text,
|
||||
this.url, {
|
||||
super.key,
|
||||
this.launchInExternalApp = true,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: text,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrlString(
|
||||
url,
|
||||
mode: launchInExternalApp
|
||||
? LaunchMode.externalApplication
|
||||
: LaunchMode.platformDefault,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'dart:io';
|
||||
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_utils/email_util.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:locker/core/constants.dart";
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/settings/about_section_widget.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
import "package:url_launcher/url_launcher_string.dart";
|
||||
|
||||
class SupportSectionWidget extends StatelessWidget {
|
||||
const SupportSectionWidget({super.key});
|
||||
|
||||
get supportEmail => null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: context.l10n.support,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Icons.help_outline_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
final String bugsEmail =
|
||||
Platform.isAndroid ? "android-bugs@ente.io" : "ios-bugs@ente.io";
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.contactSupport,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await sendEmail(context, to: supportEmail);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
AboutMenuItemWidget(
|
||||
title: context.l10n.help,
|
||||
url: "https://help.ente.io",
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.suggestFeatures,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
// ignore: unawaited_futures
|
||||
launchUrlString(
|
||||
githubDiscussionsUrl,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: context.l10n.reportABug,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
trailingIcon: Icons.chevron_right_outlined,
|
||||
trailingIconIsMuted: true,
|
||||
onTap: () async {
|
||||
await sendLogs(context, context.l10n.reportBug);
|
||||
},
|
||||
onLongPress: () async {
|
||||
final zipFilePath = await getZippedLogsFile();
|
||||
await shareLogs(context, bugsEmail, zipFilePath);
|
||||
},
|
||||
),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
95
mobile/apps/locker/lib/ui/settings/theme_switch_widget.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import "package:adaptive_theme/adaptive_theme.dart";
|
||||
import "package:ente_ui/components/captioned_text_widget.dart";
|
||||
import "package:ente_ui/components/menu_item_widget.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_ui/theme/ente_theme_data.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
import "package:locker/ui/components/expandable_menu_item_widget.dart";
|
||||
import "package:locker/ui/settings/common_settings.dart";
|
||||
|
||||
class ThemeSwitchWidget extends StatefulWidget {
|
||||
const ThemeSwitchWidget({super.key});
|
||||
|
||||
@override
|
||||
State<ThemeSwitchWidget> createState() => _ThemeSwitchWidgetState();
|
||||
}
|
||||
|
||||
class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
|
||||
AdaptiveThemeMode? currentThemeMode;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
AdaptiveTheme.getThemeMode().then(
|
||||
(value) {
|
||||
currentThemeMode = value ?? AdaptiveThemeMode.system;
|
||||
debugPrint('theme value $value');
|
||||
if (mounted) {
|
||||
setState(() => {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ExpandableMenuItemWidget(
|
||||
title: context.l10n.theme,
|
||||
selectionOptionsWidget: _getSectionOptions(context),
|
||||
leadingIcon: Theme.of(context).brightness == Brightness.light
|
||||
? Icons.light_mode_outlined
|
||||
: Icons.dark_mode_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getSectionOptions(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.light),
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.dark),
|
||||
sectionOptionSpacing,
|
||||
_menuItem(context, AdaptiveThemeMode.system),
|
||||
sectionOptionSpacing,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _name(BuildContext ctx, AdaptiveThemeMode mode) {
|
||||
switch (mode) {
|
||||
case AdaptiveThemeMode.light:
|
||||
return ctx.l10n.lightTheme;
|
||||
case AdaptiveThemeMode.dark:
|
||||
return ctx.l10n.darkTheme;
|
||||
case AdaptiveThemeMode.system:
|
||||
return ctx.l10n.systemTheme;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) {
|
||||
return MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: _name(context, themeMode),
|
||||
textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body,
|
||||
),
|
||||
pressedColor: getEnteColorScheme(context).fillFaint,
|
||||
isExpandable: false,
|
||||
trailingIcon: currentThemeMode == themeMode ? Icons.check : null,
|
||||
trailingExtraMargin: 4,
|
||||
onTap: () async {
|
||||
AdaptiveTheme.of(context).setThemeMode(themeMode);
|
||||
currentThemeMode = themeMode;
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
36
mobile/apps/locker/lib/ui/settings/title_bar_widget.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:locker/l10n/l10n.dart";
|
||||
|
||||
class SettingsTitleBarWidget extends StatelessWidget {
|
||||
const SettingsTitleBarWidget({
|
||||
super.key,
|
||||
required this.scaffoldKey,
|
||||
});
|
||||
|
||||
final GlobalKey<ScaffoldState> scaffoldKey;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 20, 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
visualDensity: const VisualDensity(horizontal: -2, vertical: -2),
|
||||
onPressed: () {
|
||||
scaffoldKey.currentState?.closeDrawer();
|
||||
},
|
||||
icon: const Icon(Icons.keyboard_double_arrow_left_outlined),
|
||||
),
|
||||
Text(l10n.settings),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -89,6 +89,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -289,7 +305,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
expandable:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: expandable
|
||||
sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2"
|
||||
@@ -445,6 +461,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.4"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@@ -605,8 +629,16 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
@@ -949,6 +981,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
pinput:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -985,10 +1025,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: f0d7856b6ca1887cfa6d1d394056a296ae33489db914e365e2044fdada449e62
|
||||
sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.2"
|
||||
version: "6.0.3"
|
||||
privacy_screen:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1065,18 +1105,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: b2961506569e28948d75ec346c28775bb111986bb69dc6a20754a457e3d97fa0
|
||||
sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.0"
|
||||
version: "11.1.0"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: "1032d392bc5d2095a77447a805aa3f804d2ae6a4d5eef5e6ebb3bd94c1bc19ef"
|
||||
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "6.1.0"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1182,34 +1222,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.1"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
|
||||
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.4.0"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b"
|
||||
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
version: "2.5.4+6"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.4.1+1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1323,13 +1363,13 @@ packages:
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
url_launcher:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.3.2"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1458,6 +1498,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.6.1"
|
||||
xmlstream:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1466,6 +1514,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
sdks:
|
||||
dart: ">=3.7.2 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
dart: ">=3.8.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
||||
@@ -4,7 +4,7 @@ publish_to: "none"
|
||||
version: 0.1.0
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
sdk: ">=3.0.0 <4.0.0"
|
||||
|
||||
dependencies:
|
||||
adaptive_theme: ^3.6.0
|
||||
@@ -35,6 +35,7 @@ dependencies:
|
||||
ente_utils:
|
||||
path: ../../packages/utils
|
||||
event_bus: ^2.0.1
|
||||
expandable: ^5.0.1
|
||||
fast_base58: ^0.2.1
|
||||
file_picker: ^10.2.0
|
||||
flutter:
|
||||
@@ -47,7 +48,6 @@ dependencies:
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
http: ^1.4.0
|
||||
intl: ^0.20.2
|
||||
io: ^1.0.5
|
||||
listen_sharing_intent: ^1.9.2
|
||||
logging: ^1.3.0
|
||||
@@ -59,10 +59,12 @@ dependencies:
|
||||
sqflite: ^2.4.1
|
||||
styled_text: ^8.1.0
|
||||
tray_manager: ^0.5.0
|
||||
url_launcher: ^6.3.2
|
||||
uuid: ^4.5.1
|
||||
window_manager: ^0.5.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
flutter_lints: ^4.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
@@ -85,3 +87,10 @@ flutter:
|
||||
- family: Montserrat
|
||||
fonts:
|
||||
- asset: assets/fonts/Montserrat-Bold.ttf
|
||||
|
||||
flutter_icons:
|
||||
adaptive_icon_foreground: "assets/launcher-icons/icon-foreground.png"
|
||||
adaptive_icon_background: "#1071ff"
|
||||
android: "icon_blue"
|
||||
ios: "IconBlue"
|
||||
image_path: "assets/launcher-icons/icon-blue.png"
|
||||
|
||||
@@ -1000,20 +1000,33 @@ class FilesDB with SqlDbBase {
|
||||
|
||||
final batch = localIDsList.sublist(i, endIndex);
|
||||
final placeholders = List.filled(batch.length, '?').join(',');
|
||||
final List<String> alreadyUploaded = [];
|
||||
// find localIDs that are already uploaded
|
||||
final result = await db.execute('''
|
||||
SELECT DISTINCT $columnLocalID
|
||||
FROM $filesTable
|
||||
WHERE $columnLocalID IN ($placeholders)
|
||||
AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID != -1)
|
||||
''');
|
||||
|
||||
for (final row in result) {
|
||||
alreadyUploaded.add(row[columnLocalID] as String);
|
||||
}
|
||||
final uploadedPlaceholders =
|
||||
alreadyUploaded.map((id) => "'$id'").join(',');
|
||||
final r = await db.execute(
|
||||
'''
|
||||
DELETE FROM $filesTable
|
||||
WHERE $columnLocalID IN ($placeholders)
|
||||
AND ($columnCollectionID IS NULL OR $columnCollectionID = -1)
|
||||
WHERE $columnLocalID IN ($uploadedPlaceholders)
|
||||
AND ($columnUploadedFileID IS NULL OR $columnUploadedFileID = -1)
|
||||
''',
|
||||
batch,
|
||||
);
|
||||
|
||||
if (r.isNotEmpty) {
|
||||
_logger
|
||||
.fine("Batch ${(i ~/ batchSize) + 1}: Removed ${r.length} files");
|
||||
_logger.warning(
|
||||
"Batch ${(i ~/ batchSize) + 1}: Removed duplicate ${r.length} files",
|
||||
);
|
||||
totalRemoved += r.length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -102,6 +103,7 @@ class UserService {
|
||||
data: {
|
||||
"email": email,
|
||||
"purpose": isChangeEmail ? "change" : purpose ?? "",
|
||||
"mobile": Platform.isIOS || Platform.isAndroid,
|
||||
},
|
||||
);
|
||||
await dialog.hide();
|
||||
|
||||
@@ -124,6 +124,10 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
],
|
||||
onGenerateRoute: (settings) {
|
||||
// On Android disabling deep links doesn't work, so this function
|
||||
// also gets triggered like /?generatedId=xyz&mainKey=abcd
|
||||
// Related: https://github.com/flutter/flutter/issues/119938
|
||||
|
||||
switch (settings.name) {
|
||||
case '/lock-screen':
|
||||
return PageRouteBuilder(
|
||||
@@ -135,7 +139,7 @@ class _AppLockState extends State<AppLock> with WidgetsBindingObserver {
|
||||
this.widget.builder(settings.arguments),
|
||||
);
|
||||
}
|
||||
return PageRouteBuilder(pageBuilder: (_, __, ___) => this._lockScreen);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import "dart:ui";
|
||||
import "package:exif_reader/exif_reader.dart";
|
||||
import 'package:flutter/painting.dart' as paint show decodeImageFromList;
|
||||
import "package:flutter_image_compress/flutter_image_compress.dart";
|
||||
import 'package:image/image.dart' as img_pkg;
|
||||
import "package:logging/logging.dart";
|
||||
import 'package:ml_linalg/linalg.dart';
|
||||
import "package:photos/models/ml/face/box.dart";
|
||||
@@ -54,45 +53,7 @@ Future<DecodedImage> decodeImageFromPath(
|
||||
String imagePath, {
|
||||
required bool includeRgbaBytes,
|
||||
required bool includeDartUiImage,
|
||||
bool inBackground = false,
|
||||
}) async {
|
||||
if (inBackground) {
|
||||
if (includeDartUiImage) {
|
||||
_logger.severe(
|
||||
"Decoding image in background with Dart UI Image is not possible!",
|
||||
);
|
||||
throw Exception(
|
||||
"Decoding image in background with Dart UI Image is not possible!",
|
||||
);
|
||||
}
|
||||
if (!includeRgbaBytes) {
|
||||
_logger.severe(
|
||||
"Decoding image in background but not returning anything",
|
||||
);
|
||||
throw Exception(
|
||||
"Decoding image in background but not returning anything",
|
||||
);
|
||||
}
|
||||
final image = await img_pkg.decodeImageFile(imagePath);
|
||||
final imageData = image?.data;
|
||||
if (imageData == null) {
|
||||
_logger.severe(
|
||||
"Failed to decode image from file: $imagePath using image package",
|
||||
);
|
||||
throw Exception(
|
||||
"Failed to decode image from file: $imagePath using image package",
|
||||
);
|
||||
}
|
||||
final bytes = imageData.getBytes(order: img_pkg.ChannelOrder.rgba);
|
||||
final dimensions = Dimensions(
|
||||
width: image!.width,
|
||||
height: image.height,
|
||||
);
|
||||
return DecodedImage(
|
||||
dimensions: dimensions,
|
||||
rawRgbaBytes: bytes,
|
||||
);
|
||||
}
|
||||
final imageData = await File(imagePath).readAsBytes();
|
||||
|
||||
final Map<String, IfdTag> exifData = await readExifFromBytes(imageData);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
- Neeraj: Fix for double enteries for local file
|
||||
- (prtk) Fix widget initial launch on iOS
|
||||
- (prtk) Upgrade Flutter version to 3.32.8
|
||||
- (prtk) Run FFMpeg in an isolate
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import "dart:convert";
|
||||
import "dart:io";
|
||||
import "dart:math";
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
@@ -88,6 +89,7 @@ class UserService {
|
||||
data: {
|
||||
"email": email,
|
||||
"purpose": isChangeEmail ? "change" : purpose ?? "",
|
||||
"mobile": Platform.isIOS || Platform.isAndroid,
|
||||
},
|
||||
);
|
||||
await dialog.hide();
|
||||
|
||||
1
mobile/packages/strings/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -1 +0,0 @@
|
||||
2edd7a931624573eff7b4280d22a7f4e
|
||||