Merge branch 'main' into clip

This commit is contained in:
vishnukvmd 2023-12-05 21:13:53 +05:30
commit 54b553909f
86 changed files with 1296 additions and 580 deletions

3
android/.gitignore vendored
View file

@ -5,3 +5,6 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Signing config files
*.jks

View file

@ -64,3 +64,5 @@ const defaultRadiusValue = 40.0;
const galleryGridSpacing = 2.0;
const searchSectionLimit = 7;
bool isInternalUser = false;

View file

@ -7,8 +7,8 @@ enum InvalidReason {
livePhotoVideoMissing,
thumbnailMissing,
unknown,
}
extension InvalidReasonExn on InvalidReason {
bool get isLivePhotoErr =>
this == InvalidReason.livePhotoToImageTypeChanged ||
@ -73,6 +73,8 @@ class InvalidStateError extends AssertionError {
class KeyDerivationError extends Error {}
class LoginKeyDerivationError extends Error {}
class SrpSetupNotCompleteError extends Error {}
class SharingNotPermittedForFreeAccountsError extends Error {}

View file

@ -716,6 +716,7 @@ class FilesDB {
Future<List<EnteFile>> getFilesCreatedWithinDurations(
List<List<int>> durations,
Set<int> ignoredCollectionIDs, {
int? visibility,
String order = 'ASC',
}) async {
if (durations.isEmpty) {
@ -731,6 +732,8 @@ class FilesDB {
")";
if (index != durations.length - 1) {
whereClause += " OR ";
} else if (visibility != null) {
whereClause += ' AND $columnMMdVisibility = $visibility';
}
}
whereClause += ")";
@ -803,7 +806,7 @@ class FilesDB {
return uploadedFileIDs;
}
Future<EnteFile?> getUploadedLocalFileInAnyCollection(
Future<List<EnteFile>> getFilesInAllCollection(
int uploadedFileID,
int userID,
) async {
@ -816,12 +819,11 @@ class FilesDB {
userID,
uploadedFileID,
],
limit: 1,
);
if (results.isEmpty) {
return null;
return <EnteFile>[];
}
return convertToFiles(results)[0];
return convertToFiles(results);
}
Future<Set<String>> getExistingLocalFileIDs(int ownerID) async {

View file

@ -24,6 +24,7 @@ class MessageLookup extends MessageLookupByLibrary {
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"addToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Add to hidden album"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
"This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),

View file

@ -442,6 +442,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Support kontaktieren"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Weiter"),
"continueOnFreeTrial": MessageLookupByLibrary.simpleMessage(
"Mit kostenloser Testversion fortfahren"),

View file

@ -23,6 +23,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m0(count) =>
"${Intl.plural(count, one: 'Add item', other: 'Add items')}";
static String m64(storageAmount, endDate) =>
"Your ${storageAmount} add-on is valid till ${endDate}";
static String m1(emailOrName) => "Added by ${emailOrName}";
static String m2(albumName) => "Added successfully to ${albumName}";
@ -135,9 +138,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m40(userEmail) =>
"${userEmail} will be removed from this shared album\n\nAny photos added by them will also be removed from the album";
static String m41(endDate) => "Renews on ${endDate}";
static String m41(endDate) => "Subscription renews on ${endDate}";
static String m64(count) =>
static String m65(count) =>
"${Intl.plural(count, one: '${count} result found', other: '${count} results found')}";
static String m42(count) => "${count} selected";
@ -190,7 +193,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1 day', other: '${count} days')}";
static String m65(endDate) => "Valid till ${endDate}";
static String m66(endDate) => "Valid till ${endDate}";
static String m60(email) => "Verify ${email}";
@ -226,6 +229,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addNew": MessageLookupByLibrary.simpleMessage("Add new"),
"addOnPageSubtitle":
MessageLookupByLibrary.simpleMessage("Details of add-ons"),
"addOnValidTill": m64,
"addOns": MessageLookupByLibrary.simpleMessage("Add-ons"),
"addPhotos": MessageLookupByLibrary.simpleMessage("Add photos"),
"addSelected": MessageLookupByLibrary.simpleMessage("Add selected"),
@ -441,6 +445,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contact support"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"contents": MessageLookupByLibrary.simpleMessage("Contents"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continue"),
"continueOnFreeTrial":
@ -559,6 +564,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"dismiss": MessageLookupByLibrary.simpleMessage("Dismiss"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut": MessageLookupByLibrary.simpleMessage("Do not sign out"),
"doThisLater": MessageLookupByLibrary.simpleMessage("Do this later"),
"doYouWantToDiscardTheEditsYouHaveMade":
MessageLookupByLibrary.simpleMessage(
@ -869,6 +875,10 @@ class MessageLookup extends MessageLookupByLibrary {
"movingFilesToAlbum":
MessageLookupByLibrary.simpleMessage("Moving files to album..."),
"name": MessageLookupByLibrary.simpleMessage("Name"),
"networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage(
"Unable to connect to Ente, please retry after sometime. If the error persists, please contact support."),
"networkHostLookUpErr": MessageLookupByLibrary.simpleMessage(
"Unable to connect to Ente, please check your network settings and contact support if the error persists."),
"never": MessageLookupByLibrary.simpleMessage("Never"),
"newAlbum": MessageLookupByLibrary.simpleMessage("New album"),
"newToEnte": MessageLookupByLibrary.simpleMessage("New to ente"),
@ -885,6 +895,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("No hidden photos or videos"),
"noImagesWithLocation":
MessageLookupByLibrary.simpleMessage("No images with location"),
"noInternetConnection":
MessageLookupByLibrary.simpleMessage("No internet connection"),
"noPhotosAreBeingBackedUpRightNow":
MessageLookupByLibrary.simpleMessage(
"No photos are being backed up right now"),
@ -933,7 +945,6 @@ class MessageLookup extends MessageLookupByLibrary {
"paymentFailedWithReason": m34,
"pendingItems": MessageLookupByLibrary.simpleMessage("Pending items"),
"pendingSync": MessageLookupByLibrary.simpleMessage("Pending sync"),
"people": MessageLookupByLibrary.simpleMessage("People"),
"peopleUsingYourCode":
MessageLookupByLibrary.simpleMessage("People using your code"),
"permDeleteWarning": MessageLookupByLibrary.simpleMessage(
@ -957,6 +968,9 @@ class MessageLookup extends MessageLookupByLibrary {
"playStoreFreeTrialValidTill": m35,
"playstoreSubscription":
MessageLookupByLibrary.simpleMessage("PlayStore subscription"),
"pleaseCheckYourInternetConnectionAndTryAgain":
MessageLookupByLibrary.simpleMessage(
"Please check your internet connection and try again."),
"pleaseContactSupportAndWeWillBeHappyToHelp":
MessageLookupByLibrary.simpleMessage(
"Please contact support@ente.io and we will be happy to help!"),
@ -1127,14 +1141,14 @@ class MessageLookup extends MessageLookupByLibrary {
"Albums, file names, and types"),
"searchHint4": MessageLookupByLibrary.simpleMessage("Location"),
"searchHint5": MessageLookupByLibrary.simpleMessage(
"Coming soon: Photo contents, faces"),
"Coming soon: Faces & magic search ✨"),
"searchHintText": MessageLookupByLibrary.simpleMessage(
"Albums, months, days, years, ..."),
"searchLocationEmptySection": MessageLookupByLibrary.simpleMessage(
"Group photos that are taken within some radius of a photo"),
"searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage(
"Invite people, and you\'ll see all photos shared by them here"),
"searchResultCount": m64,
"searchResultCount": m65,
"security": MessageLookupByLibrary.simpleMessage("Security"),
"selectAlbum": MessageLookupByLibrary.simpleMessage("Select album"),
"selectAll": MessageLookupByLibrary.simpleMessage("Select all"),
@ -1206,6 +1220,12 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Shared with you"),
"sharing": MessageLookupByLibrary.simpleMessage("Sharing..."),
"showMemories": MessageLookupByLibrary.simpleMessage("Show memories"),
"signOutFromOtherDevices":
MessageLookupByLibrary.simpleMessage("Sign out from other devices"),
"signOutOtherBody": MessageLookupByLibrary.simpleMessage(
"If you think someone might know your password, you can force all other devices using your account to sign out."),
"signOutOtherDevices":
MessageLookupByLibrary.simpleMessage("Sign out other devices"),
"signUpTerms": MessageLookupByLibrary.simpleMessage(
"I agree to the <u-terms>terms of service</u-terms> and <u-policy>privacy policy</u-policy>"),
"singleFileDeleteFromDevice": m49,
@ -1384,7 +1404,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto":
MessageLookupByLibrary.simpleMessage("Use selected photo"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Used space"),
"validTill": m65,
"validTill": m66,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Verification failed, please try again"),

View file

@ -398,6 +398,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contactar con soporte"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continuar"),
"continueOnFreeTrial": MessageLookupByLibrary.simpleMessage(
"Continuar con el plan gratuito"),

View file

@ -140,6 +140,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m41(endDate) => "Renouvellement le ${endDate}";
static String m65(count) =>
"${Intl.plural(count, one: '${count} résultat trouvé', other: '${count} résultats trouvés')}";
static String m42(count) => "${count} sélectionné(s)";
static String m43(count, yourCount) =>
@ -189,6 +192,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '0 jour', one: '1 jour', other: '${count} jours')}";
static String m66(endDate) => "Valable jusqu\'au ${endDate}";
static String m60(email) => "Vérifier ${email}";
static String m61(email) =>
@ -223,6 +228,11 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Ajouter la localisation"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("Ajouter"),
"addMore": MessageLookupByLibrary.simpleMessage("Ajouter Plus"),
"addNew": MessageLookupByLibrary.simpleMessage("Ajouter un nouveau"),
"addOnPageSubtitle": MessageLookupByLibrary.simpleMessage(
"Détails des modules complémentaires"),
"addOns":
MessageLookupByLibrary.simpleMessage("Modules complémentaires"),
"addPhotos": MessageLookupByLibrary.simpleMessage("Ajouter des photos"),
"addSelected":
MessageLookupByLibrary.simpleMessage("Ajouter la sélection"),
@ -233,6 +243,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Ajouter à un album masqué"),
"addViewer":
MessageLookupByLibrary.simpleMessage("Ajouter un observateur"),
"addYourPhotosNow": MessageLookupByLibrary.simpleMessage(
"Ajoutez vos photos maintenant"),
"addedAs": MessageLookupByLibrary.simpleMessage("Ajouté comme"),
"addedBy": m1,
"addedSuccessfullyTo": m2,
@ -356,6 +368,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Paramètres de la sauvegarde"),
"backupVideos":
MessageLookupByLibrary.simpleMessage("Sauvegarde des vidéos"),
"blackFridaySale":
MessageLookupByLibrary.simpleMessage("Offre Black Friday"),
"blog": MessageLookupByLibrary.simpleMessage("Blog"),
"cachedData":
MessageLookupByLibrary.simpleMessage("Données mises en cache"),
@ -446,6 +460,8 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contacter l\'assistance"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"contents": MessageLookupByLibrary.simpleMessage("Contenus"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continuer"),
"continueOnFreeTrial": MessageLookupByLibrary.simpleMessage(
"Poursuivre avec la version d\'essai gratuite"),
@ -510,7 +526,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Ceci supprimera tous les albums vides. Ceci est utile lorsque vous voulez réduire l\'encombrement dans votre liste d\'albums."),
"deleteAll": MessageLookupByLibrary.simpleMessage("Tout Supprimer"),
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
"This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
"Ce compte est lié à d\'autres applications ente, si vous en utilisez une.\\n\\nVos données téléchargées, dans toutes les applications ente, seront planifiées pour suppression, et votre compte sera définitivement supprimé."),
"deleteEmailRequest": MessageLookupByLibrary.simpleMessage(
"Veuillez envoyer un e-mail à <warning>account-deletion@ente.io</warning> à partir de votre adresse e-mail enregistrée."),
"deleteEmptyAlbums":
@ -658,6 +674,7 @@ class MessageLookup extends MessageLookupByLibrary {
"exportLogs": MessageLookupByLibrary.simpleMessage("Exporter les logs"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exportez vos données"),
"faces": MessageLookupByLibrary.simpleMessage("Visages"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage(
"Impossible d\'appliquer le code"),
"failedToCancel":
@ -689,7 +706,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Ajouter une description..."),
"fileSavedToGallery": MessageLookupByLibrary.simpleMessage(
"Fichier enregistré dans la galerie"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"fileTypes": MessageLookupByLibrary.simpleMessage("Types de fichiers"),
"fileTypesAndNames":
MessageLookupByLibrary.simpleMessage("Types et noms de fichiers"),
"filesBackedUpFromDevice": m19,
"filesBackedUpInAlbum": m20,
"filesDeleted":
@ -729,6 +748,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Accorder la permission"),
"groupNearbyPhotos": MessageLookupByLibrary.simpleMessage(
"Grouper les photos à proximité"),
"hearUsExplanation": MessageLookupByLibrary.simpleMessage(
"Nous ne suivons pas les installations d\'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !"),
"hearUsWhereTitle": MessageLookupByLibrary.simpleMessage(
"Comment avez-vous entendu parler de Ente? (facultatif)"),
"hidden": MessageLookupByLibrary.simpleMessage("Masqué"),
"hide": MessageLookupByLibrary.simpleMessage("Masquer"),
"hiding": MessageLookupByLibrary.simpleMessage("Masquage en cours..."),
@ -810,6 +833,7 @@ class MessageLookup extends MessageLookupByLibrary {
"linkHasExpired":
MessageLookupByLibrary.simpleMessage("Le lien a expiré"),
"linkNeverExpires": MessageLookupByLibrary.simpleMessage("Jamais"),
"livePhotos": MessageLookupByLibrary.simpleMessage("Photos en direct"),
"loadMessage1": MessageLookupByLibrary.simpleMessage(
"Vous pouvez partager votre abonnement avec votre famille"),
"loadMessage2": MessageLookupByLibrary.simpleMessage(
@ -876,7 +900,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Sécurité moyenne"),
"modifyYourQueryOrTrySearchingFor":
MessageLookupByLibrary.simpleMessage(
"Modify your query, or try searching for"),
"Modifiez votre requête, ou essayez de rechercher"),
"moments": MessageLookupByLibrary.simpleMessage("Souvenirs"),
"monthly": MessageLookupByLibrary.simpleMessage("Mensuel"),
"moveItem": m30,
"moveToAlbum":
@ -966,9 +991,12 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Supprimer définitivement"),
"permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage(
"Supprimer définitivement de l\'appareil ?"),
"photoDescriptions":
MessageLookupByLibrary.simpleMessage("Descriptions de la photo"),
"photoGridSize":
MessageLookupByLibrary.simpleMessage("Taille de la grille photo"),
"photoSmallCase": MessageLookupByLibrary.simpleMessage("photo"),
"photos": MessageLookupByLibrary.simpleMessage("Photos"),
"photosAddedByYouWillBeRemovedFromTheAlbum":
MessageLookupByLibrary.simpleMessage(
"Les photos ajoutées par vous seront retirées de l\'album"),
@ -1136,12 +1164,36 @@ class MessageLookup extends MessageLookupByLibrary {
"scanThisBarcodeWithnyourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(
"Scannez ce code-barres avec\nvotre application d\'authentification"),
"searchAlbumsEmptySection":
MessageLookupByLibrary.simpleMessage("Albums"),
"searchByAlbumNameHint":
MessageLookupByLibrary.simpleMessage("Nom de l\'album"),
"searchByExamples": MessageLookupByLibrary.simpleMessage(
"• Noms d\'albums (par exemple \"Caméra\")\n• Types de fichiers (par exemple \"Vidéos\", \".gif\")\n• Années et mois (par exemple \"2022\", \"Janvier\")\n• Vacances (par exemple \"Noël\")\n• Descriptions de photos (par exemple \"#fun\")"),
"searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage(
"Ajoutez des descriptions comme \"#trip\" dans les infos photo pour les retrouver ici plus rapidement"),
"searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
"Recherche par date, mois ou année"),
"searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
"Trouver toutes les photos d\'une personne"),
"searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("Types et noms de fichiers"),
"searchHint1": MessageLookupByLibrary.simpleMessage(
"Recherche rapide, sur l\'appareil"),
"searchHint2": MessageLookupByLibrary.simpleMessage(
"Dates des photos, descriptions"),
"searchHint3": MessageLookupByLibrary.simpleMessage(
"Albums, noms de fichiers et types"),
"searchHint4": MessageLookupByLibrary.simpleMessage("Emplacement"),
"searchHint5": MessageLookupByLibrary.simpleMessage(
"Bientôt: Visages & recherche magique ✨"),
"searchHintText": MessageLookupByLibrary.simpleMessage(
"Albums, mois, jours, années, ..."),
"searchLocationEmptySection": MessageLookupByLibrary.simpleMessage(
"Grouper les photos qui sont prises dans un certain angle d\'une photo"),
"searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage(
"Invitez des gens, et vous verrez ici toutes les photos qu\'ils partagent"),
"searchResultCount": m65,
"security": MessageLookupByLibrary.simpleMessage("Sécurité"),
"selectAlbum":
MessageLookupByLibrary.simpleMessage("Sélectionner album"),
@ -1400,6 +1452,8 @@ class MessageLookup extends MessageLookupByLibrary {
"upgrade": MessageLookupByLibrary.simpleMessage("Améliorer"),
"uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
"Envoi des fichiers vers l\'album..."),
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
"Jusqu\'à 50% de réduction, jusqu\'au 4ème déc."),
"usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage(
"Le stockage utilisable est limité par votre offre actuelle. Le stockage excédentaire deviendra automatiquement utilisable lorsque vous mettez à niveau votre offre."),
"usePublicLinksForPeopleNotOnEnte": MessageLookupByLibrary.simpleMessage(
@ -1409,6 +1463,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto": MessageLookupByLibrary.simpleMessage(
"Utiliser la photo sélectionnée"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Mémoire utilisée"),
"validTill": m66,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"La vérification a échouée, veuillez réessayer"),
@ -1426,8 +1481,11 @@ class MessageLookup extends MessageLookupByLibrary {
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(
"Vérification de la clé de récupération..."),
"videoSmallCase": MessageLookupByLibrary.simpleMessage("vidéo"),
"videos": MessageLookupByLibrary.simpleMessage("Vidéos"),
"viewActiveSessions": MessageLookupByLibrary.simpleMessage(
"Afficher les sessions actives"),
"viewAddOnButton": MessageLookupByLibrary.simpleMessage(
"Afficher les modules complémentaires"),
"viewAll": MessageLookupByLibrary.simpleMessage("Tout afficher"),
"viewAllExifData": MessageLookupByLibrary.simpleMessage(
"Visualiser toutes les données EXIF"),
@ -1481,7 +1539,7 @@ class MessageLookup extends MessageLookupByLibrary {
"youHaveSuccessfullyFreedUp": m63,
"yourAccountHasBeenDeleted":
MessageLookupByLibrary.simpleMessage("Votre compte a été supprimé"),
"yourMap": MessageLookupByLibrary.simpleMessage("Your map"),
"yourMap": MessageLookupByLibrary.simpleMessage("Votre carte"),
"yourPlanWasSuccessfullyDowngraded":
MessageLookupByLibrary.simpleMessage(
"Votre plan a été rétrogradé avec succès"),

View file

@ -187,7 +187,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1 giorno', other: '${count} giorni')}";
static String m65(endDate) => "Valido fino al ${endDate}";
static String m66(endDate) => "Valido fino al ${endDate}";
static String m60(email) => "Verifica ${email}";
@ -446,6 +446,7 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contatta il supporto"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continua"),
"continueOnFreeTrial":
MessageLookupByLibrary.simpleMessage("Continua la prova gratuita"),
@ -1390,7 +1391,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto":
MessageLookupByLibrary.simpleMessage("Usa la foto selezionata"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Spazio utilizzato"),
"validTill": m65,
"validTill": m66,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Verifica fallita, per favore prova di nuovo"),

View file

@ -24,6 +24,7 @@ class MessageLookup extends MessageLookupByLibrary {
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
"addToHiddenAlbum":
MessageLookupByLibrary.simpleMessage("Add to hidden album"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage(
"This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),

View file

@ -23,6 +23,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m0(count) =>
"${Intl.plural(count, one: 'Bestand toevoegen', other: 'Bestanden toevoegen')}";
static String m64(storageAmount, endDate) =>
"Jouw ${storageAmount} add-on is geldig tot ${endDate}";
static String m1(emailOrName) => "Toegevoegd door ${emailOrName}";
static String m2(albumName) => "Succesvol toegevoegd aan ${albumName}";
@ -140,6 +143,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m41(endDate) => "Wordt verlengd op ${endDate}";
static String m65(count) =>
"${Intl.plural(count, one: '${count} resultaat gevonden', other: '${count} resultaten gevonden')}";
static String m42(count) => "${count} geselecteerd";
static String m43(count, yourCount) =>
@ -190,6 +196,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1 dag', other: '${count} dagen')}";
static String m66(endDate) => "Geldig tot ${endDate}";
static String m60(email) => "Verifieer ${email}";
static String m61(email) =>
@ -224,6 +232,11 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Locatie toevoegen"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("Toevoegen"),
"addMore": MessageLookupByLibrary.simpleMessage("Meer toevoegen"),
"addNew": MessageLookupByLibrary.simpleMessage("Nieuwe toevoegen"),
"addOnPageSubtitle":
MessageLookupByLibrary.simpleMessage("Details van add-ons"),
"addOnValidTill": m64,
"addOns": MessageLookupByLibrary.simpleMessage("Add-ons"),
"addPhotos": MessageLookupByLibrary.simpleMessage("Foto\'s toevoegen"),
"addSelected":
MessageLookupByLibrary.simpleMessage("Voeg geselecteerde toe"),
@ -233,6 +246,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addToHiddenAlbum": MessageLookupByLibrary.simpleMessage(
"Toevoegen aan verborgen album"),
"addViewer": MessageLookupByLibrary.simpleMessage("Voeg kijker toe"),
"addYourPhotosNow":
MessageLookupByLibrary.simpleMessage("Voeg nu je foto\'s toe"),
"addedAs": MessageLookupByLibrary.simpleMessage("Toegevoegd als"),
"addedBy": m1,
"addedSuccessfullyTo": m2,
@ -354,6 +369,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Back-up instellingen"),
"backupVideos":
MessageLookupByLibrary.simpleMessage("Back-up video\'s"),
"blackFridaySale":
MessageLookupByLibrary.simpleMessage("Black Friday-aanbieding"),
"blog": MessageLookupByLibrary.simpleMessage("Blog"),
"cachedData": MessageLookupByLibrary.simpleMessage("Cachegegevens"),
"calculating": MessageLookupByLibrary.simpleMessage("Berekenen..."),
@ -441,6 +458,8 @@ class MessageLookup extends MessageLookupByLibrary {
"contactSupport":
MessageLookupByLibrary.simpleMessage("Contacteer klantenservice"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("Contacten"),
"contents": MessageLookupByLibrary.simpleMessage("Inhoud"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Doorgaan"),
"continueOnFreeTrial": MessageLookupByLibrary.simpleMessage(
"Doorgaan met gratis proefversie"),
@ -562,6 +581,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"dismiss": MessageLookupByLibrary.simpleMessage("Afwijzen"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"),
"doNotSignOut": MessageLookupByLibrary.simpleMessage("Niet uitloggen"),
"doThisLater": MessageLookupByLibrary.simpleMessage("Doe dit later"),
"doYouWantToDiscardTheEditsYouHaveMade":
MessageLookupByLibrary.simpleMessage(
@ -651,6 +671,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Logboek exporteren"),
"exportYourData":
MessageLookupByLibrary.simpleMessage("Exporteer je gegevens"),
"faces": MessageLookupByLibrary.simpleMessage("Gezichten"),
"failedToApplyCode":
MessageLookupByLibrary.simpleMessage("Code toepassen mislukt"),
"failedToCancel":
@ -684,7 +705,9 @@ class MessageLookup extends MessageLookupByLibrary {
"Voeg een beschrijving toe..."),
"fileSavedToGallery": MessageLookupByLibrary.simpleMessage(
"Bestand opgeslagen in galerij"),
"fileTypes": MessageLookupByLibrary.simpleMessage("File types"),
"fileTypes": MessageLookupByLibrary.simpleMessage("Bestandstype"),
"fileTypesAndNames":
MessageLookupByLibrary.simpleMessage("Bestandstypen en namen"),
"filesBackedUpFromDevice": m19,
"filesBackedUpInAlbum": m20,
"filesDeleted":
@ -722,6 +745,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Toestemming verlenen"),
"groupNearbyPhotos":
MessageLookupByLibrary.simpleMessage("Groep foto\'s in de buurt"),
"hearUsExplanation": MessageLookupByLibrary.simpleMessage(
"Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!"),
"hearUsWhereTitle": MessageLookupByLibrary.simpleMessage(
"Hoe hoorde je over Ente? (optioneel)"),
"hidden": MessageLookupByLibrary.simpleMessage("Verborgen"),
"hide": MessageLookupByLibrary.simpleMessage("Verbergen"),
"hiding": MessageLookupByLibrary.simpleMessage("Verbergen..."),
@ -798,6 +825,7 @@ class MessageLookup extends MessageLookupByLibrary {
"linkHasExpired":
MessageLookupByLibrary.simpleMessage("Link is vervallen"),
"linkNeverExpires": MessageLookupByLibrary.simpleMessage("Nooit"),
"livePhotos": MessageLookupByLibrary.simpleMessage("Live foto"),
"loadMessage1": MessageLookupByLibrary.simpleMessage(
"U kunt uw abonnement met uw familie delen"),
"loadMessage2": MessageLookupByLibrary.simpleMessage(
@ -862,7 +890,8 @@ class MessageLookup extends MessageLookupByLibrary {
"moderateStrength": MessageLookupByLibrary.simpleMessage("Matig"),
"modifyYourQueryOrTrySearchingFor":
MessageLookupByLibrary.simpleMessage(
"Modify your query, or try searching for"),
"Pas je zoekopdracht aan of zoek naar"),
"moments": MessageLookupByLibrary.simpleMessage("Momenten"),
"monthly": MessageLookupByLibrary.simpleMessage("Maandelijks"),
"moveItem": m30,
"moveToAlbum":
@ -875,6 +904,10 @@ class MessageLookup extends MessageLookupByLibrary {
"movingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
"Bestanden verplaatsen naar album..."),
"name": MessageLookupByLibrary.simpleMessage("Naam"),
"networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage(
"Kan geen verbinding maken met Ente, probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met support."),
"networkHostLookUpErr": MessageLookupByLibrary.simpleMessage(
"Kan geen verbinding maken met Ente, controleer uw netwerkinstellingen en neem contact op met ondersteuning als de fout zich blijft voordoen."),
"never": MessageLookupByLibrary.simpleMessage("Nooit"),
"newAlbum": MessageLookupByLibrary.simpleMessage("Nieuw album"),
"newToEnte": MessageLookupByLibrary.simpleMessage("Nieuw bij ente"),
@ -893,6 +926,8 @@ class MessageLookup extends MessageLookupByLibrary {
"Geen verborgen foto\'s of video\'s"),
"noImagesWithLocation": MessageLookupByLibrary.simpleMessage(
"Geen afbeeldingen met locatie"),
"noInternetConnection":
MessageLookupByLibrary.simpleMessage("Geen internetverbinding"),
"noPhotosAreBeingBackedUpRightNow":
MessageLookupByLibrary.simpleMessage(
"Er worden momenteel geen foto\'s geback-upt"),
@ -951,9 +986,12 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Permanent verwijderen"),
"permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage(
"Permanent verwijderen van apparaat?"),
"photoDescriptions":
MessageLookupByLibrary.simpleMessage("Foto beschrijvingen"),
"photoGridSize":
MessageLookupByLibrary.simpleMessage("Foto raster grootte"),
"photoSmallCase": MessageLookupByLibrary.simpleMessage("foto"),
"photos": MessageLookupByLibrary.simpleMessage("Foto\'s"),
"photosAddedByYouWillBeRemovedFromTheAlbum":
MessageLookupByLibrary.simpleMessage(
"Foto\'s toegevoegd door u zullen worden verwijderd uit het album"),
@ -964,6 +1002,9 @@ class MessageLookup extends MessageLookupByLibrary {
"playStoreFreeTrialValidTill": m35,
"playstoreSubscription":
MessageLookupByLibrary.simpleMessage("PlayStore abonnement"),
"pleaseCheckYourInternetConnectionAndTryAgain":
MessageLookupByLibrary.simpleMessage(
"Controleer je internetverbinding en probeer het opnieuw."),
"pleaseContactSupportAndWeWillBeHappyToHelp":
MessageLookupByLibrary.simpleMessage(
"Neem alstublieft contact op met support@ente.io en we helpen u graag!"),
@ -1118,12 +1159,36 @@ class MessageLookup extends MessageLookupByLibrary {
"scanThisBarcodeWithnyourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(
"Scan deze barcode met\nje authenticator app"),
"searchAlbumsEmptySection":
MessageLookupByLibrary.simpleMessage("Albums"),
"searchByAlbumNameHint":
MessageLookupByLibrary.simpleMessage("Albumnaam"),
"searchByExamples": MessageLookupByLibrary.simpleMessage(
"• Albumnamen (bijv. \"Camera\")\n• Types van bestanden (bijv. \"Video\'s\", \".gif\")\n• Jaren en maanden (bijv. \"2022\", \"januari\")\n• Feestdagen (bijv. \"Kerstmis\")\n• Fotobeschrijvingen (bijv. \"#fun\")"),
"searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage(
"Voeg beschrijvingen zoals \"#weekendje weg\" toe in foto-info om ze snel hier te vinden"),
"searchDatesEmptySection": MessageLookupByLibrary.simpleMessage(
"Zoeken op een datum, maand of jaar"),
"searchFaceEmptySection": MessageLookupByLibrary.simpleMessage(
"Vind alle foto\'s van een persoon"),
"searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("Bestandstypen en namen"),
"searchHint1":
MessageLookupByLibrary.simpleMessage("Snelle, lokale zoekfunctie"),
"searchHint2":
MessageLookupByLibrary.simpleMessage("Foto datums, beschrijvingen"),
"searchHint3": MessageLookupByLibrary.simpleMessage(
"Albums, bestandsnamen en typen"),
"searchHint4": MessageLookupByLibrary.simpleMessage("Locatie"),
"searchHint5": MessageLookupByLibrary.simpleMessage(
"Binnenkort beschikbaar: Gezichten & magische zoekopdrachten ✨"),
"searchHintText": MessageLookupByLibrary.simpleMessage(
"Albums, maanden, dagen, jaren, ..."),
"searchLocationEmptySection": MessageLookupByLibrary.simpleMessage(
"Foto\'s groeperen die in een bepaalde straal van een foto worden genomen"),
"searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage(
"Nodig mensen uit, en je ziet alle foto\'s die door hen worden gedeeld hier"),
"searchResultCount": m65,
"security": MessageLookupByLibrary.simpleMessage("Beveiliging"),
"selectAlbum": MessageLookupByLibrary.simpleMessage("Album selecteren"),
"selectAll": MessageLookupByLibrary.simpleMessage("Selecteer alles"),
@ -1198,6 +1263,12 @@ class MessageLookup extends MessageLookupByLibrary {
"sharing": MessageLookupByLibrary.simpleMessage("Delen..."),
"showMemories":
MessageLookupByLibrary.simpleMessage("Toon herinneringen"),
"signOutFromOtherDevices":
MessageLookupByLibrary.simpleMessage("Log uit op andere apparaten"),
"signOutOtherBody": MessageLookupByLibrary.simpleMessage(
"Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen."),
"signOutOtherDevices":
MessageLookupByLibrary.simpleMessage("Log uit op andere apparaten"),
"signUpTerms": MessageLookupByLibrary.simpleMessage(
"Ik ga akkoord met de <u-terms>gebruiksvoorwaarden</u-terms> en <u-policy>privacybeleid</u-policy>"),
"singleFileDeleteFromDevice": m49,
@ -1370,6 +1441,8 @@ class MessageLookup extends MessageLookupByLibrary {
"upgrade": MessageLookupByLibrary.simpleMessage("Upgraden"),
"uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage(
"Bestanden worden geüpload naar album..."),
"upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage(
"Tot 50% korting, tot 4 december."),
"usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage(
"Bruikbare opslag is beperkt door je huidige abonnement. Buitensporige geclaimde opslag zal automatisch bruikbaar worden wanneer je je abonnement upgrade."),
"usePublicLinksForPeopleNotOnEnte":
@ -1380,6 +1453,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto":
MessageLookupByLibrary.simpleMessage("Gebruik geselecteerde foto"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Gebruikte ruimte"),
"validTill": m66,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Verificatie mislukt, probeer het opnieuw"),
@ -1395,8 +1469,11 @@ class MessageLookup extends MessageLookupByLibrary {
"verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage(
"Herstelsleutel verifiëren..."),
"videoSmallCase": MessageLookupByLibrary.simpleMessage("video"),
"videos": MessageLookupByLibrary.simpleMessage("Video\'s"),
"viewActiveSessions":
MessageLookupByLibrary.simpleMessage("Actieve sessies bekijken"),
"viewAddOnButton":
MessageLookupByLibrary.simpleMessage("Add-ons bekijken"),
"viewAll": MessageLookupByLibrary.simpleMessage("Alles weergeven"),
"viewAllExifData":
MessageLookupByLibrary.simpleMessage("Bekijk alle EXIF gegevens"),
@ -1448,7 +1525,7 @@ class MessageLookup extends MessageLookupByLibrary {
"youHaveSuccessfullyFreedUp": m63,
"yourAccountHasBeenDeleted":
MessageLookupByLibrary.simpleMessage("Je account is verwijderd"),
"yourMap": MessageLookupByLibrary.simpleMessage("Your map"),
"yourMap": MessageLookupByLibrary.simpleMessage("Jouw kaart"),
"yourPlanWasSuccessfullyDowngraded":
MessageLookupByLibrary.simpleMessage(
"Uw abonnement is succesvol gedegradeerd"),

View file

@ -33,6 +33,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Bekreft sletting av konto"),
"confirmDeletePrompt": MessageLookupByLibrary.simpleMessage(
"Ja, jeg ønsker å slette denne kontoen og all dataen dens permanent."),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"deleteAccount": MessageLookupByLibrary.simpleMessage("Slett konto"),
"deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage(
"Vi er lei oss for at du forlater oss. Gi oss gjerne en tilbakemelding så vi kan forbedre oss."),

View file

@ -50,6 +50,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Powtórz hasło"),
"contactSupport": MessageLookupByLibrary.simpleMessage(
"Skontaktuj się z pomocą techniczną"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Kontynuuj"),
"createAccount": MessageLookupByLibrary.simpleMessage("Stwórz konto"),
"createNewAccount":

View file

@ -136,6 +136,7 @@ class MessageLookup extends MessageLookupByLibrary {
"Confirme sua chave de recuperação"),
"contactSupport":
MessageLookupByLibrary.simpleMessage("Falar com o suporte"),
"contacts": MessageLookupByLibrary.simpleMessage("Contacts"),
"continueLabel": MessageLookupByLibrary.simpleMessage("Continuar"),
"copypasteThisCodentoYourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage(

View file

@ -23,6 +23,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m0(count) =>
"${Intl.plural(count, one: '添加一个项目', other: '添加一些项目')}";
static String m64(storageAmount, endDate) =>
"您的 ${storageAmount} 插件有效期至 ${endDate}";
static String m1(emailOrName) => "${emailOrName} 添加";
static String m2(albumName) => "成功添加到 ${albumName}";
@ -127,6 +130,9 @@ class MessageLookup extends MessageLookupByLibrary {
static String m41(endDate) => "${endDate} 前续费";
static String m65(count) =>
"${Intl.plural(count, other: '已找到 ${count} 个结果')}";
static String m42(count) => "已选择 ${count}";
static String m43(count, yourCount) => "选择了 ${count} 个 (您的 ${yourCount} 个)";
@ -170,7 +176,7 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1天', other: '${count} 天')}";
static String m65(endDate) => "有效期至 ${endDate}";
static String m66(endDate) => "有效期至 ${endDate}";
static String m60(email) => "验证 ${email}";
@ -198,7 +204,9 @@ class MessageLookup extends MessageLookupByLibrary {
"addLocation": MessageLookupByLibrary.simpleMessage("添加地点"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("添加"),
"addMore": MessageLookupByLibrary.simpleMessage("添加更多"),
"addNew": MessageLookupByLibrary.simpleMessage("新建"),
"addOnPageSubtitle": MessageLookupByLibrary.simpleMessage("附加组件详情"),
"addOnValidTill": m64,
"addOns": MessageLookupByLibrary.simpleMessage("附加组件"),
"addPhotos": MessageLookupByLibrary.simpleMessage("添加照片"),
"addSelected": MessageLookupByLibrary.simpleMessage("添加所选项"),
@ -206,6 +214,7 @@ class MessageLookup extends MessageLookupByLibrary {
"addToEnte": MessageLookupByLibrary.simpleMessage("添加到 ente"),
"addToHiddenAlbum": MessageLookupByLibrary.simpleMessage("添加到隐藏相册"),
"addViewer": MessageLookupByLibrary.simpleMessage("添加查看者"),
"addYourPhotosNow": MessageLookupByLibrary.simpleMessage("立即添加您的照片"),
"addedAs": MessageLookupByLibrary.simpleMessage("已添加为"),
"addedBy": m1,
"addedSuccessfullyTo": m2,
@ -308,6 +317,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("通过移动数据备份"),
"backupSettings": MessageLookupByLibrary.simpleMessage("备份设置"),
"backupVideos": MessageLookupByLibrary.simpleMessage("备份视频"),
"blackFridaySale": MessageLookupByLibrary.simpleMessage("黑色星期五特惠"),
"blog": MessageLookupByLibrary.simpleMessage("博客"),
"cachedData": MessageLookupByLibrary.simpleMessage("缓存数据"),
"calculating": MessageLookupByLibrary.simpleMessage("正在计算..."),
@ -374,6 +384,8 @@ class MessageLookup extends MessageLookupByLibrary {
"contactFamilyAdmin": m9,
"contactSupport": MessageLookupByLibrary.simpleMessage("联系支持"),
"contactToManageSubscription": m10,
"contacts": MessageLookupByLibrary.simpleMessage("联系人"),
"contents": MessageLookupByLibrary.simpleMessage("内容"),
"continueLabel": MessageLookupByLibrary.simpleMessage("继续"),
"continueOnFreeTrial": MessageLookupByLibrary.simpleMessage("继续免费试用"),
"convertToAlbum": MessageLookupByLibrary.simpleMessage("转换为相册"),
@ -466,6 +478,7 @@ class MessageLookup extends MessageLookupByLibrary {
"discord": MessageLookupByLibrary.simpleMessage("Discord"),
"dismiss": MessageLookupByLibrary.simpleMessage("忽略"),
"distanceInKMUnit": MessageLookupByLibrary.simpleMessage("公里"),
"doNotSignOut": MessageLookupByLibrary.simpleMessage("不要退登"),
"doThisLater": MessageLookupByLibrary.simpleMessage("稍后再做"),
"doYouWantToDiscardTheEditsYouHaveMade":
MessageLookupByLibrary.simpleMessage("您想要放弃您所做的编辑吗?"),
@ -535,6 +548,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("此链接已过期。请选择新的过期时间或禁用链接过期。"),
"exportLogs": MessageLookupByLibrary.simpleMessage("导出日志"),
"exportYourData": MessageLookupByLibrary.simpleMessage("导出您的数据"),
"faces": MessageLookupByLibrary.simpleMessage("人脸"),
"failedToApplyCode": MessageLookupByLibrary.simpleMessage("无法应用代码"),
"failedToCancel": MessageLookupByLibrary.simpleMessage("取消失败"),
"failedToDownloadVideo": MessageLookupByLibrary.simpleMessage("视频下载失败"),
@ -558,6 +572,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("无法将文件保存到相册"),
"fileInfoAddDescHint": MessageLookupByLibrary.simpleMessage("添加说明..."),
"fileSavedToGallery": MessageLookupByLibrary.simpleMessage("文件已保存到相册"),
"fileTypes": MessageLookupByLibrary.simpleMessage("文件类型"),
"fileTypesAndNames": MessageLookupByLibrary.simpleMessage("文件类型和名称"),
"filesBackedUpFromDevice": m19,
"filesBackedUpInAlbum": m20,
"filesDeleted": MessageLookupByLibrary.simpleMessage("文件已删除"),
@ -655,6 +671,7 @@ class MessageLookup extends MessageLookupByLibrary {
"linkExpiry": MessageLookupByLibrary.simpleMessage("链接过期"),
"linkHasExpired": MessageLookupByLibrary.simpleMessage("链接已过期"),
"linkNeverExpires": MessageLookupByLibrary.simpleMessage("永不"),
"livePhotos": MessageLookupByLibrary.simpleMessage("实况照片"),
"loadMessage1": MessageLookupByLibrary.simpleMessage("您可以与家庭分享您的订阅"),
"loadMessage2":
MessageLookupByLibrary.simpleMessage("到目前为止我们已经保存了1 000多万个回忆"),
@ -710,8 +727,8 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("移动端, 网页端, 桌面端"),
"moderateStrength": MessageLookupByLibrary.simpleMessage("中等"),
"modifyYourQueryOrTrySearchingFor":
MessageLookupByLibrary.simpleMessage(
"Modify your query, or try searching for"),
MessageLookupByLibrary.simpleMessage("修改您的查询,或尝试搜索"),
"moments": MessageLookupByLibrary.simpleMessage("瞬间"),
"monthly": MessageLookupByLibrary.simpleMessage("每月"),
"moveItem": m30,
"moveToAlbum": MessageLookupByLibrary.simpleMessage("移动到相册"),
@ -721,6 +738,10 @@ class MessageLookup extends MessageLookupByLibrary {
"movingFilesToAlbum":
MessageLookupByLibrary.simpleMessage("正在将文件移动到相册..."),
"name": MessageLookupByLibrary.simpleMessage("名称"),
"networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage(
"无法连接到 Ente请稍后重试。如果错误仍然存在请联系支持人员。"),
"networkHostLookUpErr": MessageLookupByLibrary.simpleMessage(
"无法连接到 Ente请检查您的网络设置如果错误仍然存在请联系支持人员。"),
"never": MessageLookupByLibrary.simpleMessage("永不"),
"newAlbum": MessageLookupByLibrary.simpleMessage("新建相册"),
"newToEnte": MessageLookupByLibrary.simpleMessage("刚来到ente"),
@ -737,6 +758,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("没有隐藏的照片或视频"),
"noImagesWithLocation":
MessageLookupByLibrary.simpleMessage("没有带有位置的图像"),
"noInternetConnection": MessageLookupByLibrary.simpleMessage("无互联网连接"),
"noPhotosAreBeingBackedUpRightNow":
MessageLookupByLibrary.simpleMessage("目前没有照片正在备份"),
"noPhotosFoundHere": MessageLookupByLibrary.simpleMessage("这里没有找到照片"),
@ -784,8 +806,10 @@ class MessageLookup extends MessageLookupByLibrary {
"permanentlyDelete": MessageLookupByLibrary.simpleMessage("永久删除"),
"permanentlyDeleteFromDevice":
MessageLookupByLibrary.simpleMessage("要从设备中永久删除吗?"),
"photoDescriptions": MessageLookupByLibrary.simpleMessage("照片说明"),
"photoGridSize": MessageLookupByLibrary.simpleMessage("照片网格大小"),
"photoSmallCase": MessageLookupByLibrary.simpleMessage("照片"),
"photos": MessageLookupByLibrary.simpleMessage("照片"),
"photosAddedByYouWillBeRemovedFromTheAlbum":
MessageLookupByLibrary.simpleMessage("您添加的照片将从相册中移除"),
"pickCenterPoint": MessageLookupByLibrary.simpleMessage("选择中心点"),
@ -793,6 +817,8 @@ class MessageLookup extends MessageLookupByLibrary {
"playStoreFreeTrialValidTill": m35,
"playstoreSubscription":
MessageLookupByLibrary.simpleMessage("PlayStore 订阅"),
"pleaseCheckYourInternetConnectionAndTryAgain":
MessageLookupByLibrary.simpleMessage("请检查您的互联网连接,然后重试。"),
"pleaseContactSupportAndWeWillBeHappyToHelp":
MessageLookupByLibrary.simpleMessage(
"请用英语联系 support@ente.io ,我们将乐意提供帮助!"),
@ -908,10 +934,29 @@ class MessageLookup extends MessageLookupByLibrary {
"scanCode": MessageLookupByLibrary.simpleMessage("扫描代码"),
"scanThisBarcodeWithnyourAuthenticatorApp":
MessageLookupByLibrary.simpleMessage("用您的身份验证器应用\n扫描此条码"),
"searchAlbumsEmptySection": MessageLookupByLibrary.simpleMessage("相册"),
"searchByAlbumNameHint": MessageLookupByLibrary.simpleMessage("相册名称"),
"searchByExamples": MessageLookupByLibrary.simpleMessage(
"• 相册名称(例如“相机”)\n• 文件类型(例如“视频”、“.gif”\n• 年份和月份例如“2022”、“一月”\n• 假期(例如“圣诞节”)\n• 照片说明(例如“#和女儿独居,好开心啊”)"),
"searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage(
"在照片信息中添加“#旅游”等描述,以便在此处快速找到它们"),
"searchDatesEmptySection":
MessageLookupByLibrary.simpleMessage("按日期搜索,月份或年份"),
"searchFaceEmptySection":
MessageLookupByLibrary.simpleMessage("查找一个人的所有照片"),
"searchFileTypesAndNamesEmptySection":
MessageLookupByLibrary.simpleMessage("文件类型和名称"),
"searchHint1": MessageLookupByLibrary.simpleMessage("在设备上快速搜索"),
"searchHint2": MessageLookupByLibrary.simpleMessage("照片日期、描述"),
"searchHint3": MessageLookupByLibrary.simpleMessage("相册、文件名和类型"),
"searchHint4": MessageLookupByLibrary.simpleMessage("位置"),
"searchHint5": MessageLookupByLibrary.simpleMessage("即将到来:面部和魔法搜索✨"),
"searchHintText": MessageLookupByLibrary.simpleMessage("相册,月,日,年,..."),
"searchLocationEmptySection":
MessageLookupByLibrary.simpleMessage("在照片的一定半径内拍摄的几组照片"),
"searchPeopleEmptySection":
MessageLookupByLibrary.simpleMessage("邀请他人,您将在此看到他们分享的所有照片"),
"searchResultCount": m65,
"security": MessageLookupByLibrary.simpleMessage("安全"),
"selectAlbum": MessageLookupByLibrary.simpleMessage("选择相册"),
"selectAll": MessageLookupByLibrary.simpleMessage("全选"),
@ -973,6 +1018,11 @@ class MessageLookup extends MessageLookupByLibrary {
"sharedWithYou": MessageLookupByLibrary.simpleMessage("已与您共享"),
"sharing": MessageLookupByLibrary.simpleMessage("正在分享..."),
"showMemories": MessageLookupByLibrary.simpleMessage("显示回忆"),
"signOutFromOtherDevices":
MessageLookupByLibrary.simpleMessage("从其他设备退出登录"),
"signOutOtherBody": MessageLookupByLibrary.simpleMessage(
"如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。"),
"signOutOtherDevices": MessageLookupByLibrary.simpleMessage("登出其他设备"),
"signUpTerms": MessageLookupByLibrary.simpleMessage(
"我同意 <u-terms>服务条款</u-terms> 和 <u-policy>隐私政策</u-policy>"),
"singleFileDeleteFromDevice": m49,
@ -1113,6 +1163,8 @@ class MessageLookup extends MessageLookupByLibrary {
"upgrade": MessageLookupByLibrary.simpleMessage("升级"),
"uploadingFilesToAlbum":
MessageLookupByLibrary.simpleMessage("正在将文件上传到相册..."),
"upto50OffUntil4thDec":
MessageLookupByLibrary.simpleMessage("最高五折优惠直至12月4日。"),
"usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage(
"可用存储空间受您当前计划的限制。 当您升级您的计划时,超出要求的存储空间将自动变为可用。"),
"usePublicLinksForPeopleNotOnEnte":
@ -1120,7 +1172,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useRecoveryKey": MessageLookupByLibrary.simpleMessage("使用恢复密钥"),
"useSelectedPhoto": MessageLookupByLibrary.simpleMessage("使用所选照片"),
"usedSpace": MessageLookupByLibrary.simpleMessage("已用空间"),
"validTill": m65,
"validTill": m66,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage("验证失败,请重试"),
"verificationId": MessageLookupByLibrary.simpleMessage("验证 ID"),
@ -1133,6 +1185,7 @@ class MessageLookup extends MessageLookupByLibrary {
"verifyingRecoveryKey":
MessageLookupByLibrary.simpleMessage("正在验证恢复密钥..."),
"videoSmallCase": MessageLookupByLibrary.simpleMessage("视频"),
"videos": MessageLookupByLibrary.simpleMessage("视频"),
"viewActiveSessions": MessageLookupByLibrary.simpleMessage("查看活动会话"),
"viewAddOnButton": MessageLookupByLibrary.simpleMessage("查看附加组件"),
"viewAll": MessageLookupByLibrary.simpleMessage("查看全部"),
@ -1178,7 +1231,7 @@ class MessageLookup extends MessageLookupByLibrary {
"youHaveSuccessfullyFreedUp": m63,
"yourAccountHasBeenDeleted":
MessageLookupByLibrary.simpleMessage("您的账户已删除"),
"yourMap": MessageLookupByLibrary.simpleMessage("Your map"),
"yourMap": MessageLookupByLibrary.simpleMessage("您的地图"),
"yourPlanWasSuccessfullyDowngraded":
MessageLookupByLibrary.simpleMessage("您的计划已成功降级"),
"yourPlanWasSuccessfullyUpgraded":

View file

@ -3954,10 +3954,10 @@ class S {
);
}
/// `Renews on {endDate}`
/// `Subscription renews on {endDate}`
String renewsOn(Object endDate) {
return Intl.message(
'Renews on $endDate',
'Subscription renews on $endDate',
name: 'renewsOn',
desc: '',
args: [endDate],
@ -3984,6 +3984,16 @@ class S {
);
}
/// `Your {storageAmount} add-on is valid till {endDate}`
String addOnValidTill(Object storageAmount, Object endDate) {
return Intl.message(
'Your $storageAmount add-on is valid till $endDate',
name: 'addOnValidTill',
desc: '',
args: [storageAmount, endDate],
);
}
/// `Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.`
String playStoreFreeTrialValidTill(Object endDate) {
return Intl.message(
@ -6579,6 +6589,26 @@ class S {
);
}
/// `Unable to connect to Ente, please check your network settings and contact support if the error persists.`
String get networkHostLookUpErr {
return Intl.message(
'Unable to connect to Ente, please check your network settings and contact support if the error persists.',
name: 'networkHostLookUpErr',
desc: '',
args: [],
);
}
/// `Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.`
String get networkConnectionRefusedErr {
return Intl.message(
'Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.',
name: 'networkConnectionRefusedErr',
desc: '',
args: [],
);
}
/// `Cached data`
String get cachedData {
return Intl.message(
@ -8055,10 +8085,10 @@ class S {
);
}
/// `Coming soon: Photo contents, faces`
/// `Coming soon: Faces & magic search `
String get searchHint5 {
return Intl.message(
'Coming soon: Photo contents, faces',
'Coming soon: Faces & magic search ✨',
name: 'searchHint5',
desc: '',
args: [],
@ -8127,6 +8157,66 @@ class S {
args: [],
);
}
/// `No internet connection`
String get noInternetConnection {
return Intl.message(
'No internet connection',
name: 'noInternetConnection',
desc: '',
args: [],
);
}
/// `Please check your internet connection and try again.`
String get pleaseCheckYourInternetConnectionAndTryAgain {
return Intl.message(
'Please check your internet connection and try again.',
name: 'pleaseCheckYourInternetConnectionAndTryAgain',
desc: '',
args: [],
);
}
/// `Sign out from other devices`
String get signOutFromOtherDevices {
return Intl.message(
'Sign out from other devices',
name: 'signOutFromOtherDevices',
desc: '',
args: [],
);
}
/// `If you think someone might know your password, you can force all other devices using your account to sign out.`
String get signOutOtherBody {
return Intl.message(
'If you think someone might know your password, you can force all other devices using your account to sign out.',
name: 'signOutOtherBody',
desc: '',
args: [],
);
}
/// `Sign out other devices`
String get signOutOtherDevices {
return Intl.message(
'Sign out other devices',
name: 'signOutOtherDevices',
desc: '',
args: [],
);
}
/// `Do not sign out`
String get doNotSignOut {
return Intl.message(
'Do not sign out',
name: 'doNotSignOut',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View file

@ -561,9 +561,10 @@
"type": "text"
},
"faqs": "FAQs",
"renewsOn": "Renews on {endDate}",
"renewsOn": "Subscription renews on {endDate}",
"freeTrialValidTill": "Free trial valid till {endDate}",
"validTill": "Valid till {endDate}",
"addOnValidTill": "Your {storageAmount} add-on is valid till {endDate}",
"playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.",
"subWillBeCancelledOn": "Your subscription will be cancelled on {endDate}",
"subscription": "Subscription",
@ -934,6 +935,8 @@
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
"error": "Error",
"tempErrorContactSupportIfPersists": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.",
"networkHostLookUpErr" : "Unable to connect to Ente, please check your network settings and contact support if the error persists.",
"networkConnectionRefusedErr" : "Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.",
"cachedData": "Cached data",
"clearCaches": "Clear caches",
"remoteImages": "Remote images",
@ -1155,5 +1158,12 @@
"@addNew": {
"description": "Text to add a new item (location tag, album, caption etc)"
},
"contacts": "Contacts"
"contacts": "Contacts",
"noInternetConnection": "No internet connection",
"pleaseCheckYourInternetConnectionAndTryAgain": "Please check your internet connection and try again.",
"signOutFromOtherDevices": "Sign out from other devices",
"signOutOtherBody": "If you think someone might know your password, you can force all other devices using your account to sign out.",
"signOutOtherDevices": "Sign out other devices",
"doNotSignOut": "Do not sign out"
}

View file

@ -557,6 +557,7 @@
"faqs": "FAQ",
"renewsOn": "Renouvellement le {endDate}",
"freeTrialValidTill": "Essai gratuit valide jusquau {endDate}",
"validTill": "Valable jusqu'au {endDate}",
"playStoreFreeTrialValidTill": "Essai gratuit valable jusqu'à {endDate}.\nVous pouvez choisir un plan payant par la suite.",
"subWillBeCancelledOn": "Votre abonnement sera annulé le {endDate}",
"subscription": "Abonnement",
@ -955,12 +956,22 @@
"loadMessage7": "Nos applications mobiles s'exécutent en arrière-plan pour chiffrer et sauvegarder automatiquement les nouvelles photos que vous prenez",
"loadMessage8": "web.ente.io dispose d'un outil de téléchargement facile à utiliser",
"loadMessage9": "Nous utilisons Xchacha20Poly1305 pour chiffrer vos données en toute sécurité",
"photoDescriptions": "Descriptions de la photo",
"fileTypesAndNames": "Types et noms de fichiers",
"location": "Emplacement",
"moments": "Souvenirs",
"searchFaceEmptySection": "Trouver toutes les photos d'une personne",
"searchDatesEmptySection": "Recherche par date, mois ou année",
"searchLocationEmptySection": "Grouper les photos qui sont prises dans un certain angle d'une photo",
"searchPeopleEmptySection": "Invitez des gens, et vous verrez ici toutes les photos qu'ils partagent",
"searchAlbumsEmptySection": "Albums",
"searchFileTypesAndNamesEmptySection": "Types et noms de fichiers",
"searchCaptionEmptySection": "Ajoutez des descriptions comme \"#trip\" dans les infos photo pour les retrouver ici plus rapidement",
"language": "Langue",
"selectLanguage": "Sélectionner une langue",
"locationName": "Nom du lieu",
"addLocation": "Ajouter la localisation",
"groupNearbyPhotos": "Grouper les photos à proximité",
"location": "Emplacement",
"kiloMeterUnit": "km",
"addLocationButton": "Ajouter",
"radius": "Rayon",
@ -1102,9 +1113,41 @@
"crashReporting": "Rapports d'erreurs",
"addToHiddenAlbum": "Ajouter à un album masqué",
"moveToHiddenAlbum": "Déplacer vers un album masqué",
"fileTypes": "File types",
"deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"yourMap": "Your map",
"modifyYourQueryOrTrySearchingFor": "Modify your query, or try searching for",
"fileTypes": "Types de fichiers",
"deleteConfirmDialogBody": "Ce compte est lié à d'autres applications ente, si vous en utilisez une.\\n\\nVos données téléchargées, dans toutes les applications ente, seront planifiées pour suppression, et votre compte sera définitivement supprimé.",
"hearUsWhereTitle": "Comment avez-vous entendu parler de Ente? (facultatif)",
"hearUsExplanation": "Nous ne suivons pas les installations d'applications. Il serait utile que vous nous disiez comment vous nous avez trouvés !",
"viewAddOnButton": "Afficher les modules complémentaires",
"addOns": "Modules complémentaires",
"addOnPageSubtitle": "Détails des modules complémentaires",
"yourMap": "Votre carte",
"modifyYourQueryOrTrySearchingFor": "Modifiez votre requête, ou essayez de rechercher",
"blackFridaySale": "Offre Black Friday",
"upto50OffUntil4thDec": "Jusqu'à 50% de réduction, jusqu'au 4ème déc.",
"photos": "Photos",
"videos": "Vidéos",
"livePhotos": "Photos en direct",
"searchHint1": "Recherche rapide, sur l'appareil",
"searchHint2": "Dates des photos, descriptions",
"searchHint3": "Albums, noms de fichiers et types",
"searchHint4": "Emplacement",
"searchHint5": "Bientôt: Visages & recherche magique ✨",
"addYourPhotosNow": "Ajoutez vos photos maintenant",
"searchResultCount": "{count, plural, one{{count} résultat trouvé} other{{count} résultats trouvés}}",
"@searchResultCount": {
"description": "Text to tell user how many results were found for their search query",
"placeholders": {
"count": {
"example": "1|2|3",
"type": "int"
}
}
},
"faces": "Visages",
"contents": "Contenus",
"addNew": "Ajouter un nouveau",
"@addNew": {
"description": "Text to add a new item (location tag, album, caption etc)"
},
"contacts": "Contacts"
}

View file

@ -557,6 +557,8 @@
"faqs": "Veelgestelde vragen",
"renewsOn": "Wordt verlengd op {endDate}",
"freeTrialValidTill": "Gratis proefversie geldig tot {endDate}",
"validTill": "Geldig tot {endDate}",
"addOnValidTill": "Jouw {storageAmount} add-on is geldig tot {endDate}",
"playStoreFreeTrialValidTill": "Gratis proefperiode geldig tot {endDate}.\nU kunt naderhand een betaald abonnement kiezen.",
"subWillBeCancelledOn": "Uw abonnement loopt af op {endDate}",
"subscription": "Abonnement",
@ -927,6 +929,8 @@
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Het lijkt erop dat er iets fout is gegaan. Probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met ons supportteam.",
"error": "Foutmelding",
"tempErrorContactSupportIfPersists": "Het lijkt erop dat er iets fout is gegaan. Probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met ons supportteam.",
"networkHostLookUpErr": "Kan geen verbinding maken met Ente, controleer uw netwerkinstellingen en neem contact op met ondersteuning als de fout zich blijft voordoen.",
"networkConnectionRefusedErr": "Kan geen verbinding maken met Ente, probeer het later opnieuw. Als de fout zich blijft voordoen, neem dan contact op met support.",
"cachedData": "Cachegegevens",
"clearCaches": "Cache legen",
"remoteImages": "Externe afbeeldingen",
@ -955,12 +959,22 @@
"loadMessage7": "Onze mobiele apps draaien op de achtergrond om alle nieuwe foto's die je maakt te versleutelen en te back-uppen",
"loadMessage8": "web.ente.io heeft een vlotte uploader",
"loadMessage9": "We gebruiken Xchacha20Poly1305 om uw gegevens veilig te versleutelen",
"photoDescriptions": "Foto beschrijvingen",
"fileTypesAndNames": "Bestandstypen en namen",
"location": "Locatie",
"moments": "Momenten",
"searchFaceEmptySection": "Vind alle foto's van een persoon",
"searchDatesEmptySection": "Zoeken op een datum, maand of jaar",
"searchLocationEmptySection": "Foto's groeperen die in een bepaalde straal van een foto worden genomen",
"searchPeopleEmptySection": "Nodig mensen uit, en je ziet alle foto's die door hen worden gedeeld hier",
"searchAlbumsEmptySection": "Albums",
"searchFileTypesAndNamesEmptySection": "Bestandstypen en namen",
"searchCaptionEmptySection": "Voeg beschrijvingen zoals \"#weekendje weg\" toe in foto-info om ze snel hier te vinden",
"language": "Taal",
"selectLanguage": "Taal selecteren",
"locationName": "Locatie naam",
"addLocation": "Locatie toevoegen",
"groupNearbyPhotos": "Groep foto's in de buurt",
"location": "Locatie",
"kiloMeterUnit": "km",
"addLocationButton": "Toevoegen",
"radius": "Straal",
@ -1102,9 +1116,47 @@
"crashReporting": "Crash rapportering",
"addToHiddenAlbum": "Toevoegen aan verborgen album",
"moveToHiddenAlbum": "Verplaatsen naar verborgen album",
"fileTypes": "File types",
"fileTypes": "Bestandstype",
"deleteConfirmDialogBody": "Dit account is gekoppeld aan andere ente apps, als je er gebruik van maakt.\\n\\nJe geüploade gegevens worden in alle ente apps gepland voor verwijdering, en je account wordt permanent verwijderd voor alle ente diensten.",
"yourMap": "Your map",
"modifyYourQueryOrTrySearchingFor": "Modify your query, or try searching for",
"contacts": "Contacts"
"hearUsWhereTitle": "Hoe hoorde je over Ente? (optioneel)",
"hearUsExplanation": "Wij gebruiken geen tracking. Het zou helpen als je ons vertelt waar je ons gevonden hebt!",
"viewAddOnButton": "Add-ons bekijken",
"addOns": "Add-ons",
"addOnPageSubtitle": "Details van add-ons",
"yourMap": "Jouw kaart",
"modifyYourQueryOrTrySearchingFor": "Pas je zoekopdracht aan of zoek naar",
"blackFridaySale": "Black Friday-aanbieding",
"upto50OffUntil4thDec": "Tot 50% korting, tot 4 december.",
"photos": "Foto's",
"videos": "Video's",
"livePhotos": "Live foto",
"searchHint1": "Snelle, lokale zoekfunctie",
"searchHint2": "Foto datums, beschrijvingen",
"searchHint3": "Albums, bestandsnamen en typen",
"searchHint4": "Locatie",
"searchHint5": "Binnenkort beschikbaar: Gezichten & magische zoekopdrachten ✨",
"addYourPhotosNow": "Voeg nu je foto's toe",
"searchResultCount": "{count, plural, one{{count} resultaat gevonden} other{{count} resultaten gevonden}}",
"@searchResultCount": {
"description": "Text to tell user how many results were found for their search query",
"placeholders": {
"count": {
"example": "1|2|3",
"type": "int"
}
}
},
"faces": "Gezichten",
"contents": "Inhoud",
"addNew": "Nieuwe toevoegen",
"@addNew": {
"description": "Text to add a new item (location tag, album, caption etc)"
},
"contacts": "Contacten",
"noInternetConnection": "Geen internetverbinding",
"pleaseCheckYourInternetConnectionAndTryAgain": "Controleer je internetverbinding en probeer het opnieuw.",
"signOutFromOtherDevices": "Log uit op andere apparaten",
"signOutOtherBody": "Als je denkt dat iemand je wachtwoord zou kunnen kennen, kun je alle andere apparaten die je account gebruiken dwingen om uit te loggen.",
"signOutOtherDevices": "Log uit op andere apparaten",
"doNotSignOut": "Niet uitloggen"
}

View file

@ -558,6 +558,7 @@
"renewsOn": "在 {endDate} 前续费",
"freeTrialValidTill": "免费试用有效期至 {endDate}",
"validTill": "有效期至 {endDate}",
"addOnValidTill": "您的 {storageAmount} 插件有效期至 {endDate}",
"playStoreFreeTrialValidTill": "免费试用有效期至 {endDate}。\n之后您可以选择付费计划。",
"subWillBeCancelledOn": "您的订阅将于 {endDate} 取消",
"subscription": "订阅",
@ -928,6 +929,8 @@
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。",
"error": "错误",
"tempErrorContactSupportIfPersists": "看起来出了点问题。 请稍后重试。 如果错误仍然存在,请联系我们的支持团队。",
"networkHostLookUpErr": "无法连接到 Ente请检查您的网络设置如果错误仍然存在请联系支持人员。",
"networkConnectionRefusedErr": "无法连接到 Ente请稍后重试。如果错误仍然存在请联系支持人员。",
"cachedData": "缓存数据",
"clearCaches": "清除缓存",
"remoteImages": "远程图像",
@ -1149,5 +1152,11 @@
"@addNew": {
"description": "Text to add a new item (location tag, album, caption etc)"
},
"contacts": "联系人"
"contacts": "联系人",
"noInternetConnection": "无互联网连接",
"pleaseCheckYourInternetConnectionAndTryAgain": "请检查您的互联网连接,然后重试。",
"signOutFromOtherDevices": "从其他设备退出登录",
"signOutOtherBody": "如果你认为有人可能知道你的密码,你可以强制所有使用你账户的其他设备退出登录。",
"signOutOtherDevices": "登出其他设备",
"doNotSignOut": "不要退登"
}

View file

@ -2,6 +2,7 @@ import "package:photos/core/configuration.dart";
import "package:photos/models/file/file.dart";
import "package:photos/models/file/file_type.dart";
import "package:photos/models/file/trash_file.dart";
import "package:photos/services/collections_service.dart";
extension FilePropsExtn on EnteFile {
bool get isLivePhoto => fileType == FileType.livePhoto;
@ -21,4 +22,14 @@ extension FilePropsExtn on EnteFile {
bool get isCollect => uploaderName != null;
String? get uploaderName => pubMagicMetadata?.uploaderName;
bool canReUpload(int userID) =>
localID != null &&
localID!.isNotEmpty &&
isOwner &&
collectionID != null &&
(CollectionsService.instance
.getCollectionByID(collectionID!)
?.isOwner(userID) ??
false);
}

View file

@ -223,7 +223,7 @@ extension SectionTypeExtensions on SectionType {
},
);
if (result is Exception) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: result);
}
};
default:

View file

@ -196,7 +196,7 @@ class BillingService {
);
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
await dialog.hide();
}

View file

@ -71,9 +71,10 @@ class FeatureFlagService {
bool isInternalUserOrDebugBuild() {
final String? email = Configuration.instance.getEmail();
final userID = Configuration.instance.getUserID();
return (email != null && email.endsWith("@ente.io")) ||
isInternalUser = (email != null && email.endsWith("@ente.io")) ||
_internalUserIDs.contains(userID) ||
kDebugMode;
return isInternalUser;
}
Future<void> fetchFeatureFlags() async {

View file

@ -154,7 +154,7 @@ extension HiddenService on CollectionsService {
} catch (e, s) {
_logger.severe("Could not hide", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
} finally {
await dialog.hide();

View file

@ -114,17 +114,29 @@ class LocalFileUpdateService {
Future<void> _checkAndMarkFilesWithDifferentHashForFileUpdate(
List<String> localIDsToProcess,
) async {
_logger.info("files to process ${localIDsToProcess.length} for reupload");
final int userID = Configuration.instance.getUserID()!;
final List<EnteFile> result =
await FilesDB.instance.getLocalFiles(localIDsToProcess);
final List<EnteFile> localFilesForUser = [];
final Set<String> localIDsWithFile = {};
for (EnteFile file in result) {
if (file.ownerID == null || file.ownerID == userID) {
localFilesForUser.add(file);
localIDsWithFile.add(file.localID!);
}
}
final Set<String> processedIDs = {};
// if a file for localID doesn't exist, then mark it as processed
// otherwise the app will be stuck in retrying same set of ids
for (String localID in localIDsToProcess) {
if (!localIDsWithFile.contains(localID)) {
processedIDs.add(localID);
}
}
_logger.info("files to process ${localIDsToProcess.length} for reupload, "
"missing localFile cnt ${processedIDs.length}");
for (EnteFile file in localFilesForUser) {
if (processedIDs.contains(file.localID)) {
continue;
@ -156,7 +168,6 @@ class LocalFileUpdateService {
} on InvalidFileError catch (e) {
if (e.reason == InvalidReason.livePhotoToImageTypeChanged ||
e.reason == InvalidReason.imageToLivePhotoTypeChanged) {
late FileType fileType;
if (e.reason == InvalidReason.livePhotoToImageTypeChanged) {
fileType = FileType.image;

View file

@ -8,6 +8,7 @@ import "package:photos/events/files_updated_event.dart";
import "package:photos/events/memories_setting_changed.dart";
import 'package:photos/models/filters/important_items_filter.dart';
import 'package:photos/models/memory.dart';
import "package:photos/models/metadata/common_keys.dart";
import 'package:photos/services/collections_service.dart';
import "package:shared_preferences/shared_preferences.dart";
@ -108,6 +109,7 @@ class MemoriesService extends ChangeNotifier {
final files = await _filesDB.getFilesCreatedWithinDurations(
durations,
ignoredCollections,
visibility: visibleVisibility,
);
final seenTimes = await _memoriesDB.getSeenTimes();
final List<Memory> memories = [];

View file

@ -554,12 +554,27 @@ class RemoteSyncService {
.info("Skipping some updated files as we are throttling uploads");
break;
}
final file = await _db.getUploadedLocalFileInAnyCollection(
final allFiles = await _db.getFilesInAllCollection(
uploadedFileID,
ownerID,
);
if (file != null) {
_uploadFile(file, file.collectionID!, futures);
if (allFiles.isEmpty) {
_logger.warning("No files found for uploadedFileID $uploadedFileID");
continue;
}
EnteFile? fileInCollectionOwnedByUser;
for (final file in allFiles) {
if (file.canReUpload(ownerID)) {
fileInCollectionOwnedByUser = file;
break;
}
}
if (fileInCollectionOwnedByUser != null) {
_uploadFile(
fileInCollectionOwnedByUser,
fileInCollectionOwnedByUser.collectionID!,
futures,
);
}
}

View file

@ -34,11 +34,10 @@ import "package:photos/ui/account/recovery_page.dart";
import 'package:photos/ui/account/two_factor_authentication_page.dart';
import 'package:photos/ui/account/two_factor_recovery_page.dart';
import 'package:photos/ui/account/two_factor_setup_page.dart';
import "package:photos/ui/components/buttons/button_widget.dart";
import "package:photos/ui/common/progress_dialog.dart";
import "package:photos/ui/tabs/home_widget.dart";
import 'package:photos/utils/crypto_util.dart';
import 'package:photos/utils/dialog_util.dart';
import "package:photos/utils/email_util.dart";
import 'package:photos/utils/navigation_util.dart';
import 'package:photos/utils/toast_util.dart';
import "package:pointycastle/export.dart";
@ -114,8 +113,9 @@ class UserService {
),
);
return;
} else {
throw Exception("send-ott action failed, non-200");
}
unawaited(showGenericErrorDialog(context: context));
} on DioError catch (e) {
await dialog.hide();
_logger.info(e);
@ -128,12 +128,14 @@ class UserService {
),
);
} else {
unawaited(showGenericErrorDialog(context: context));
unawaited(showGenericErrorDialog(context: context, error: e));
}
} catch (e) {
} catch (e, s) {
await dialog.hide();
_logger.severe(e);
unawaited(showGenericErrorDialog(context: context));
_logger.severe(e, s);
unawaited(
showGenericErrorDialog(context: context, error: e),
);
}
}
@ -260,7 +262,7 @@ class UserService {
//to close and only then to show the error dialog.
Future.delayed(
const Duration(milliseconds: 150),
() => showGenericErrorDialog(context: context),
() => showGenericErrorDialog(context: context, error: null),
);
}
}
@ -280,7 +282,7 @@ class UserService {
}
} catch (e) {
_logger.severe(e);
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
return null;
}
}
@ -503,6 +505,7 @@ class UserService {
Future<void> registerOrUpdateSrp(
Uint8List loginKey, {
SetKeysRequest? setKeysRequest,
bool logOutOtherDevices = false,
}) async {
try {
final String username = const Uuid().v4().toString();
@ -559,6 +562,7 @@ class UserService {
'setupID': setupSRPResponse.setupID,
'srpM1': base64Encode(SRP6Util.encodeBigInt(clientM!)),
'updatedKeyAttr': setKeysRequest.toMap(),
'logOutOtherDevices': logOutOtherDevices,
},
);
}
@ -586,127 +590,100 @@ class UserService {
BuildContext context,
SrpAttributes srpAttributes,
String userPassword,
ProgressDialog dialog,
) async {
final dialog = createProgressDialog(
context,
S.of(context).pleaseWait,
isDismissible: true,
);
await dialog.show();
late Uint8List keyEncryptionKey;
try {
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
_logger.finest('Start deriving key');
keyEncryptionKey = await CryptoUtil.deriveKey(
utf8.encode(userPassword) as Uint8List,
CryptoUtil.base642bin(srpAttributes.kekSalt),
srpAttributes.memLimit,
srpAttributes.opsLimit,
);
_logger.finest('keyDerivation done, derive LoginKey');
final loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
final Uint8List identity = Uint8List.fromList(
utf8.encode(srpAttributes.srpUserID),
);
_logger.finest('longinKey derivation done');
final Uint8List salt = base64Decode(srpAttributes.srpSalt);
final Uint8List password = loginKey;
final SecureRandom random = _getSecureRandom();
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final client = SRP6Client(
group: kDefaultSrpGroup,
digest: Digest('SHA-256'),
random: random,
);
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final A = client.generateClientCredentials(salt, identity, password);
final createSessionResponse = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/create-session",
data: {
"srpUserID": srpAttributes.srpUserID,
"srpA": base64Encode(SRP6Util.encodeBigInt(A!)),
},
);
final String sessionID = createSessionResponse.data["sessionID"];
final String srpB = createSessionResponse.data["srpB"];
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
setTwoFactor(value: true);
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomeWidget();
} else {
throw Exception("unexpected response during email verification");
}
}
await dialog.hide();
if (page is HomeWidget) {
Navigator.of(context).popUntil((route) => route.isFirst);
Bus.instance.fire(AccountConfiguredEvent());
} else {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
final serverB = SRP6Util.decodeBigInt(base64Decode(srpB));
// ignore: need to calculate secret to get M1, unused_local_variable
final clientS = client.calculateSecret(serverB);
final clientM = client.calculateClientEvidenceMessage();
final response = await _dio.post(
_config.getHttpEndpoint() + "/users/srp/verify-session",
data: {
"sessionID": sessionID,
"srpUserID": srpAttributes.srpUserID,
"srpM1": base64Encode(SRP6Util.encodeBigInt(clientM!)),
},
);
if (response.statusCode == 200) {
Widget page;
final String twoFASessionID = response.data["twoFactorSessionID"];
Configuration.instance.setVolatilePassword(userPassword);
if (twoFASessionID.isNotEmpty) {
setTwoFactor(value: true);
page = TwoFactorAuthenticationPage(twoFASessionID);
} else {
await _saveConfiguration(response);
if (Configuration.instance.getEncryptedToken() != null) {
await Configuration.instance.decryptSecretsAndGetKeyEncKey(
userPassword,
Configuration.instance.getKeyAttributes()!,
keyEncryptionKey: keyEncryptionKey,
);
page = const HomeWidget();
} else {
throw Exception("unexpected response during email verification");
}
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
await _showContactSupportDialog(
context,
S.of(context).incorrectPasswordTitle,
S.of(context).pleaseTryAgain,
);
if (page is HomeWidget) {
Navigator.of(context).popUntil((route) => route.isFirst);
Bus.instance.fire(AccountConfiguredEvent());
} else {
_logger.severe('failed to verify password', e, s);
await _showContactSupportDialog(
context,
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (BuildContext context) {
return page;
},
),
(route) => route.isFirst,
);
}
} catch (e, s) {
_logger.severe('failed to verify password', e, s);
await dialog.hide();
await _showContactSupportDialog(
context,
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
);
} else {
// should never reach here
throw Exception("unexpected response during email verification");
}
}
Future<void> updateKeyAttributes(
KeyAttributes keyAttributes,
Uint8List loginKey,
) async {
Uint8List loginKey, {
required bool logoutOtherDevices,
}) async {
try {
final setKeyRequest = SetKeysRequest(
kekSalt: keyAttributes.kekSalt,
@ -715,7 +692,11 @@ class UserService {
memLimit: keyAttributes.memLimit!,
opsLimit: keyAttributes.opsLimit!,
);
await registerOrUpdateSrp(loginKey, setKeysRequest: setKeyRequest);
await registerOrUpdateSrp(
loginKey,
setKeysRequest: setKeyRequest,
logOutOtherDevices: logoutOtherDevices,
);
await _config.setKeyAttributes(keyAttributes);
} catch (e) {
_logger.severe(e);
@ -978,7 +959,7 @@ class UserService {
try {
recoveryKey = await getOrCreateRecoveryKey(context);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
}
final dialog = createProgressDialog(context, S.of(context).verifying);
@ -1164,26 +1145,4 @@ class UserService {
rethrow;
}
}
Future<void> _showContactSupportDialog(
BuildContext context,
String title,
String message,
) async {
final dialogChoice = await showChoiceDialog(
context,
title: title,
body: message,
firstButtonLabel: S.of(context).contactSupport,
secondButtonLabel: S.of(context).ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
S.of(context).contactSupport,
"support@ente.io",
postShare: () {},
);
}
}
}

View file

@ -1,10 +1,12 @@
import "dart:async";
import "package:flutter/foundation.dart";
import "package:flutter/material.dart";
import "package:logging/logging.dart";
import "package:photos/core/constants.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/events/files_updated_event.dart";
import "package:photos/events/tab_changed_event.dart";
import "package:photos/models/search/search_result.dart";
import "package:photos/models/search/search_types.dart";
import "package:photos/utils/debouncer.dart";
@ -29,24 +31,52 @@ class _AllSectionsExamplesProviderState
Future<List<List<SearchResult>>> allSectionsExamplesFuture = Future.value([]);
late StreamSubscription<FilesUpdatedEvent> _filesUpdatedEvent;
late StreamSubscription<TabChangedEvent> _tabChangeEvent;
bool hasPendingUpdate = false;
bool isOnSearchTab = false;
final _logger = Logger("AllSectionsExamplesProvider");
final _debouncer =
Debouncer(const Duration(seconds: 3), executionInterval: 6000);
final _debouncer = Debouncer(
const Duration(seconds: 3),
executionInterval: const Duration(seconds: 12),
);
@override
void initState() {
super.initState();
//add all common events for all search sections to reload to here.
_filesUpdatedEvent = Bus.instance.on<FilesUpdatedEvent>().listen((event) {
reloadAllSections();
if (!isOnSearchTab) {
if (kDebugMode) {
_logger.finest('Skip reload till user clicks on search tab');
}
hasPendingUpdate = true;
return;
} else {
hasPendingUpdate = false;
reloadAllSections();
}
});
_tabChangeEvent = Bus.instance.on<TabChangedEvent>().listen((event) {
if (event.source == TabChangedEventSource.pageView &&
event.selectedIndex == 3) {
isOnSearchTab = true;
if (hasPendingUpdate) {
hasPendingUpdate = false;
reloadAllSections();
}
} else {
isOnSearchTab = false;
}
});
reloadAllSections();
}
void reloadAllSections() {
_logger.info('queue reload all sections');
_debouncer.run(() async {
setState(() {
_logger.info("reloading all sections in search tab");
_logger.info("'_debounceTimer: reloading all sections in search tab");
final allSectionsExamples = <Future<List<SearchResult>>>[];
for (SectionType sectionType in SectionType.values) {
if (sectionType == SectionType.face ||
@ -66,6 +96,7 @@ class _AllSectionsExamplesProviderState
@override
void dispose() {
_filesUpdatedEvent.cancel();
_tabChangeEvent.cancel();
_debouncer.cancelDebounce();
super.dispose();
}

View file

@ -262,7 +262,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
isDismissible: false,
);
if (choice!.action == ButtonAction.error) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: choice.exception);
}
}
@ -289,7 +289,7 @@ class _DeleteAccountPageState extends State<DeleteAccountPage> {
showShortToast(context, S.of(context).yourAccountHasBeenDeleted);
} catch (e, s) {
Logger("DeleteAccount").severe("failed to delete", e, s);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}

View file

@ -1,12 +1,17 @@
import "package:dio/dio.dart";
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import "package:logging/logging.dart";
import 'package:photos/core/configuration.dart';
import "package:photos/core/errors.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/api/user/srp.dart";
import "package:photos/services/user_service.dart";
import "package:photos/theme/ente_theme.dart";
import 'package:photos/ui/common/dynamic_fab.dart';
import "package:photos/ui/components/buttons/button_widget.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/email_util.dart";
// LoginPasswordVerificationPage is a page that allows the user to enter their password to verify their identity.
// If the password is correct, then the user is either directed to
@ -31,6 +36,7 @@ class _LoginPasswordVerificationPageState
String? email;
bool _passwordInFocus = false;
bool _passwordVisible = false;
final Logger _logger = Logger("LoginPasswordVerificationPage");
@override
void initState() {
@ -85,11 +91,7 @@ class _LoginPasswordVerificationPageState
buttonText: S.of(context).logInLabel,
onPressedFunction: () async {
FocusScope.of(context).unfocus();
await UserService.instance.verifyEmailViaPassword(
context,
widget.srpAttributes,
_passwordController.text,
);
await verifyPassword(context, _passwordController.text);
},
),
floatingActionButtonLocation: fabLocation(),
@ -97,6 +99,106 @@ class _LoginPasswordVerificationPageState
);
}
Future<void> verifyPassword(BuildContext context, String password) async {
final dialog = createProgressDialog(
context,
S.of(context).pleaseWait,
isDismissible: true,
);
await dialog.show();
try {
await UserService.instance.verifyEmailViaPassword(
context,
widget.srpAttributes,
password,
dialog,
);
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
_logger.severe('server reject, failed verify SRP login', e, s);
await _showContactSupportDialog(
context,
S.of(context).incorrectPasswordTitle,
S.of(context).pleaseTryAgain,
);
} else {
_logger.severe('API failure during SRP login', e, s);
if (e.type == DioErrorType.other) {
await _showContactSupportDialog(
context,
S.of(context).noInternetConnection,
S.of(context).pleaseCheckYourInternetConnectionAndTryAgain,
);
} else {
await _showContactSupportDialog(
context,
S.of(context).somethingWentWrong,
S.of(context).verificationFailedPleaseTryAgain,
);
}
}
} catch (e, s) {
_logger.info('error during loginViaPassword', e);
await dialog.hide();
if (e is LoginKeyDerivationError) {
_logger.severe('loginKey derivation error', e, s);
// LoginKey err, perform regular login via ott verification
await UserService.instance.sendOtt(
context,
email!,
isCreateAccountScreen: true,
);
return;
} else if (e is KeyDerivationError) {
// device is not powerful enough to perform derive key
final dialogChoice = await showChoiceDialog(
context,
title: S.of(context).recreatePasswordTitle,
body: S.of(context).recreatePasswordBody,
firstButtonLabel: S.of(context).useRecoveryKey,
);
if (dialogChoice!.action == ButtonAction.first) {
await UserService.instance.sendOtt(
context,
email!,
isResetPasswordScreen: true,
);
}
return;
} else {
_logger.severe('unexpected error while verifying password', e, s);
await _showContactSupportDialog(
context,
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
);
}
}
}
Future<void> _showContactSupportDialog(
BuildContext context,
String title,
String message,
) async {
final dialogChoice = await showChoiceDialog(
context,
title: title,
body: message,
firstButtonLabel: S.of(context).contactSupport,
secondButtonLabel: S.of(context).ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
S.of(context).contactSupport,
"support@ente.io",
postShare: () {},
);
}
}
Widget _getBody() {
return Column(
children: [

View file

@ -7,11 +7,13 @@ import 'package:photos/core/event_bus.dart';
import 'package:photos/events/account_configured_event.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/l10n/l10n.dart";
import "package:photos/models/key_gen_result.dart";
import 'package:photos/services/user_service.dart';
import 'package:photos/ui/account/recovery_key_page.dart';
import 'package:photos/ui/common/dynamic_fab.dart';
import 'package:photos/ui/common/web_page.dart';
import "package:photos/ui/components/models/button_type.dart";
import 'package:photos/ui/payment/subscription.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/navigation_util.dart';
@ -27,9 +29,10 @@ enum PasswordEntryMode {
class PasswordEntryPage extends StatefulWidget {
final PasswordEntryMode mode;
const PasswordEntryPage({required this.mode, Key?
key,})
: super(key: key);
const PasswordEntryPage({
required this.mode,
Key? key,
}) : super(key: key);
@override
State<PasswordEntryPage> createState() => _PasswordEntryPageState();
@ -379,13 +382,18 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
}
void _updatePassword() async {
final logOutFromOthers = await logOutFromOtherDevices(context);
final dialog =
createProgressDialog(context, S.of(context).generatingEncryptionKeys);
await dialog.show();
try {
final result = await Configuration.instance
.getAttributesForNewPassword(_passwordController1.text);
await UserService.instance.updateKeyAttributes(result.item1, result.item2);
await UserService.instance.updateKeyAttributes(
result.item1,
result.item2,
logoutOtherDevices: logOutFromOthers,
);
await dialog.hide();
showShortToast(context, S.of(context).passwordChangedSuccessfully);
Navigator.of(context).pop();
@ -396,16 +404,37 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
Future<bool> logOutFromOtherDevices(BuildContext context) async {
bool logOutFromOther = true;
await showChoiceDialog(
context,
title: context.l10n.signOutFromOtherDevices,
body: context.l10n.signOutOtherBody,
isDismissible: false,
firstButtonLabel: context.l10n.signOutOtherDevices,
firstButtonType: ButtonType.critical,
firstButtonOnTap: () async {
logOutFromOther = true;
},
secondButtonLabel: context.l10n.doNotSignOut,
secondButtonOnTap: () async {
logOutFromOther = false;
},
);
return logOutFromOther;
}
Future<void> _showRecoveryCodeDialog(String password) async {
final dialog =
createProgressDialog(context, S.of(context).generatingEncryptionKeys);
await dialog.show();
try {
final KeyGenResult result = await Configuration.instance.generateKey(password);
final KeyGenResult result =
await Configuration.instance.generateKey(password);
Configuration.instance.setVolatilePassword(null);
await dialog.hide();
onDone() async {
@ -427,7 +456,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
@ -452,7 +481,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
S.of(context).sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease,
);
} else {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
}

View file

@ -51,7 +51,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
"Please check your internet connection and try again.",
);
} else {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
}
return;
}
@ -109,7 +109,7 @@ class _VerifyRecoveryPageState extends State<VerifyRecoveryPage> {
),
);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return;
}
}

View file

@ -73,7 +73,7 @@ extension CollectionFileActions on CollectionActions {
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: bContext);
showGenericErrorDialog(context: bContext, error: actionResult.exception);
} else {
selectedFiles.clearAll();
}
@ -187,7 +187,7 @@ extension CollectionFileActions on CollectionActions {
} catch (e, s) {
logger.severe("Failed to add to album", e, s);
await dialog?.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
rethrow;
}
}

View file

@ -52,7 +52,7 @@ class CollectionActions {
_showUnSupportedAlert(context);
} else {
logger.severe("Failed to update shareUrl collection", e);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
return false;
}
@ -93,7 +93,7 @@ class CollectionActions {
);
if (actionResult?.action != null) {
if (actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: actionResult.exception);
}
return actionResult.action == ButtonAction.first;
} else {
@ -142,7 +142,7 @@ class CollectionActions {
return collection;
} catch (e, s) {
dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
logger.severe("Failing to create link for selected files", e, s);
}
return null;
@ -183,7 +183,7 @@ class CollectionActions {
);
if (actionResult?.action != null) {
if (actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: actionResult.exception);
}
return actionResult.action == ButtonAction.first;
}
@ -230,7 +230,7 @@ class CollectionActions {
} catch (e) {
await dialog?.hide();
logger.severe("Failed to get public key", e);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
}
// getPublicKey can return null when no user is associated with given
@ -272,7 +272,7 @@ class CollectionActions {
_showUnSupportedAlert(context);
} else {
logger.severe("failed to share collection", e);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
return false;
}
@ -353,7 +353,7 @@ class CollectionActions {
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: bContext);
showGenericErrorDialog(context: bContext, error: actionResult.exception);
return false;
}
if ((actionResult?.action != null) &&

View file

@ -125,7 +125,7 @@ Future<void> showSingleFileDeleteSheet(
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: actionResult.exception);
}
}

View file

@ -111,6 +111,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
if (result is Exception) {
showGenericErrorDialog(
context: context,
error: result,
);
_logger.severe(
"Failed to name album",
@ -310,7 +311,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
);
return true;
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
}
}
@ -333,7 +334,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
),
);
} else {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: result);
_logger.severe("Cannot share collections owned by others");
}
}
@ -352,7 +353,10 @@ class AlbumVerticalListWidget extends StatelessWidget {
),
);
} else {
showGenericErrorDialog(context: context);
showGenericErrorDialog(
context: context,
error: Exception("Can not share collection owned by others"),
);
_logger.severe("Cannot share collections owned by others");
}
return Future.value(true);
@ -409,7 +413,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
} catch (e, s) {
_logger.severe("Could not move to album", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
}
}
@ -438,7 +442,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
} catch (e, s) {
_logger.severe("Could not move to album", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return false;
}
}

View file

@ -12,6 +12,7 @@ import 'package:photos/models/device_collection.dart';
import "package:photos/ui/collections/device/device_folder_item.dart";
import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/utils/debouncer.dart";
class DeviceFoldersGridView extends StatefulWidget {
const DeviceFoldersGridView({
@ -27,6 +28,10 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription;
String _loadReason = "init";
final _logger = Logger((_DeviceFoldersGridViewState).toString());
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
@override
void initState() {
@ -39,8 +44,12 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
});
_localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason;
setState(() {});
_debouncer.run(() async {
if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
});
super.initState();
@ -93,6 +102,7 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
void dispose() {
_backupFoldersUpdatedEvent?.cancel();
_localFilesSubscription?.cancel();
_debouncer.cancelDebounce();
super.dispose();
}
}

View file

@ -13,6 +13,7 @@ import 'package:photos/models/device_collection.dart';
import "package:photos/ui/collections/device/device_folder_item.dart";
import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/utils/debouncer.dart";
class DeviceFolderVerticalGridView extends StatelessWidget {
final Widget? appTitle;
@ -58,6 +59,10 @@ class _DeviceFolderVerticalGridViewBodyState
StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription;
String _loadReason = "init";
final logger = Logger((_DeviceFolderVerticalGridViewBodyState).toString());
final _debouncer = Debouncer(
const Duration(milliseconds: 1500),
executionInterval: const Duration(seconds: 4),
);
/*
Aspect ratio 1:1 Max width 224 Fixed gap 8
Width changes dynamically with screen width such that we can fit 2 in one row.
@ -78,8 +83,12 @@ class _DeviceFolderVerticalGridViewBodyState
});
_localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason;
setState(() {});
_debouncer.run(() async {
if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
});
super.initState();
}
@ -149,6 +158,7 @@ class _DeviceFolderVerticalGridViewBodyState
void dispose() {
_backupFoldersUpdatedEvent?.cancel();
_localFilesSubscription?.cancel();
_debouncer.cancelDebounce();
super.dispose();
}
}

View file

@ -55,7 +55,7 @@ class NewAlbumIcon extends StatelessWidget {
},
);
if (result is Exception) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: result);
}
},
);

View file

@ -498,7 +498,7 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
} else if (exception != null) {
//This is to show the execution was unsuccessful if the dialog is manually
//closed before the execution completes.
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: exception);
}
}
}

View file

@ -28,7 +28,7 @@ class HomeGalleryWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final double bottomSafeArea = MediaQuery.of(context).padding.bottom;
final double bottomSafeArea = MediaQuery.paddingOf(context).bottom;
final gallery = Gallery(
asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
final ownerID = Configuration.instance.getUserID();
@ -81,6 +81,8 @@ class HomeGalleryWidget extends StatelessWidget {
footer: footer,
// scrollSafe area -> SafeArea + Preserver more + Nav Bar buttons
scrollBottomSafeArea: bottomSafeArea + 180,
reloadDebounceTime: const Duration(seconds: 2),
reloadDebounceExecutionInterval: const Duration(seconds: 5),
);
return Stack(
alignment: Alignment.bottomCenter,

View file

@ -36,7 +36,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
bool _showCounter = false;
bool _showStepIndicator = true;
PageController? _pageController;
bool _shouldDisableScroll = false;
final bool _shouldDisableScroll = false;
late int currentUserID;
final GlobalKey shareButtonKey = GlobalKey();
@ -327,14 +327,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
file,
autoPlay: false,
tagPrefix: "memories",
shouldDisableScroll: (value) {
if (value == _shouldDisableScroll) {
return;
}
setState(() {
_shouldDisableScroll = value;
});
},
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),

View file

@ -9,7 +9,7 @@ class MapGalleryTileBadge extends StatelessWidget {
if (number <= 99) {
return number.toString();
} else if (number <= 999) {
return '${(number / 100).toStringAsFixed(0)}00+';
return '${(number / 100).floor().toStringAsFixed(0)}00+';
} else if (number >= 1000 && number < 2000) {
return '1K+';
} else {

View file

@ -40,8 +40,10 @@ class MapView extends StatefulWidget {
class _MapViewState extends State<MapView> {
late List<Marker> _markers;
final _debouncer =
Debouncer(const Duration(milliseconds: 300), executionInterval: 750);
final _debouncer = Debouncer(
const Duration(milliseconds: 300),
executionInterval: const Duration(milliseconds: 750),
);
@override
void initState() {

View file

@ -1,16 +1,12 @@
import 'package:flutter/material.dart';
import "package:flutter_animate/flutter_animate.dart";
import "package:photos/generated/l10n.dart";
import 'package:photos/services/update_service.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/buttons/button_widget.dart';
import 'package:photos/ui/components/divider_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
import "package:photos/ui/components/notification_widget.dart";
import 'package:photos/ui/components/title_bar_title_widget.dart';
import 'package:photos/ui/notification/update/change_log_entry.dart';
import "package:photos/utils/black_friday_util.dart";
import "package:url_launcher/url_launcher_string.dart";
class ChangeLogPage extends StatefulWidget {
const ChangeLogPage({
@ -67,36 +63,6 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
shouldShowBfBanner()
? RepaintBoundary(
child: Padding(
padding: const EdgeInsets.only(bottom: 8),
child: NotificationWidget(
isBlackFriday: true,
startIcon: Icons.celebration,
actionIcon: Icons.arrow_forward_outlined,
text: S.of(context).blackFridaySale,
subText: S.of(context).upto50OffUntil4thDec,
type: NotificationType.goldenBanner,
onTap: () async {
launchUrlString(
"https://ente.io/blackfriday",
mode: LaunchMode.platformDefault,
);
},
),
)
.animate(
onPlay: (controller) => controller.repeat(),
)
.shimmer(
duration: 1000.ms,
delay: 3200.ms,
size: 0.6,
),
)
: const SizedBox.shrink(),
ButtonWidget(
buttonType: ButtonType.trailingIconPrimary,
buttonSize: ButtonSize.large,
@ -112,33 +78,16 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
const SizedBox(
height: 8,
),
// ButtonWidget(
// buttonType: ButtonType.trailingIconSecondary,
// buttonSize: ButtonSize.large,
// labelText: S.of(context).rateTheApp,
// icon: Icons.favorite_rounded,
// iconColor: enteColorScheme.primary500,
// onTap: () async {
// await UpdateService.instance.launchReviewUrl();
// },
// ),
shouldShowBfBanner()
? const SizedBox.shrink()
: ButtonWidget(
buttonType: ButtonType.trailingIconSecondary,
buttonSize: ButtonSize.large,
labelText: "Join the ente community",
icon: Icons.people_alt_rounded,
iconColor: enteColorScheme.primary500,
onTap: () async {
launchUrlString(
"https://ente.io/community",
mode: LaunchMode.externalApplication,
);
},
),
ButtonWidget(
buttonType: ButtonType.trailingIconSecondary,
buttonSize: ButtonSize.large,
labelText: S.of(context).rateTheApp,
icon: Icons.favorite_rounded,
iconColor: enteColorScheme.primary500,
onTap: () async {
await UpdateService.instance.launchReviewUrl();
},
),
const SizedBox(height: 8),
],
),
@ -160,14 +109,6 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
'\nYou can now discover items that come under different Locations, Moments, Contacts, Photo descriptions, Albums and File types with ease.\n',
),
);
items.add(
ChangeLogEntry(
"Black Friday Sale 🎉",
"You can now purchase Ente's plans for 3 years at 30% off and 5 years at 50% off!\n"
'\nThe storage you purchase will be stacked on top of your current plan.\n'
'\nThis is the lowest our prices will ever be, so do consider upgrading!\n',
),
);
return Container(
padding: const EdgeInsets.only(left: 16),

View file

@ -123,7 +123,7 @@ class ChildSubscriptionWidget extends StatelessWidget {
},
);
if (choice!.action == ButtonAction.error) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: choice.exception);
}
}
}

View file

@ -190,7 +190,10 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
} else {
// should never reach here
_logger.severe("unexpected status", uri.toString());
showGenericErrorDialog(context: context);
showGenericErrorDialog(
context: context,
error: Exception("expected payment status $paymentStatus"),
);
}
}

View file

@ -52,6 +52,7 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
late ProgressDialog _dialog;
late UserDetails _userDetails;
late bool _hasActiveSubscription;
bool _hideCurrentPlanSelection = false;
late FreePlan _freePlan;
late List<BillingPlan> _plans;
bool _hasLoadedData = false;
@ -177,7 +178,10 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
_userService.getUserDetailsV2(memoryCount: false).then((userDetails) async {
_userDetails = userDetails;
_currentSubscription = userDetails.subscription;
_hasActiveSubscription = _currentSubscription!.isValid();
_hideCurrentPlanSelection =
_currentSubscription?.attributes?.isCancelled ?? false;
showYearlyPlan = _currentSubscription!.isYearlyPlan();
final billingPlans = await _billingService.getBillingPlans();
_isActiveStripeSubscriber =
@ -239,7 +243,7 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
widgets.add(_showSubscriptionToggle());
}
if (_hasActiveSubscription) {
if (_currentSubscription != null) {
widgets.add(
ValidityWidget(
currentSubscription: _currentSubscription,
@ -458,7 +462,7 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
storage: plan.storage,
price: plan.price,
period: plan.period,
isActive: isActive,
isActive: isActive && !_hideCurrentPlanSelection,
),
),
),
@ -520,11 +524,14 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
final ProductDetailsResponse response =
await InAppPurchase.instance.queryProductDetails({productID});
if (response.notFoundIDs.isNotEmpty) {
_logger.severe(
"Could not find products: " + response.notFoundIDs.toString(),
);
final errMsg = "Could not find products: " +
response.notFoundIDs.toString();
_logger.severe(errMsg);
await _dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(
context: context,
error: Exception(errMsg),
);
return;
}
final isCrossGradingOnAndroid = Platform.isAndroid &&

View file

@ -53,6 +53,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
// indicates if user's subscription plan is still active
late bool _hasActiveSubscription;
bool _hideCurrentPlanSelection = false;
late FreePlan _freePlan;
List<BillingPlan> _plans = [];
bool _hasLoadedData = false;
@ -73,7 +74,11 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
.then((userDetails) async {
_userDetails = userDetails;
_currentSubscription = userDetails.subscription;
_showYearlyPlan = _currentSubscription!.isYearlyPlan();
_hideCurrentPlanSelection =
(_currentSubscription?.attributes?.isCancelled ?? false) &&
userDetails.hasPaidAddon();
_hasActiveSubscription = _currentSubscription!.isValid();
_isStripeSubscriber = _currentSubscription!.paymentProvider == stripe;
return _filterStripeForUI().then((value) {
@ -210,7 +215,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
widgets.add(_showSubscriptionToggle());
if (_hasActiveSubscription) {
if (_currentSubscription != null) {
widgets.add(
ValidityWidget(
currentSubscription: _currentSubscription,
@ -332,7 +337,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
).then((value) => onWebPaymentGoBack);
} catch (e) {
await _dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
await _dialog.hide();
}
@ -340,6 +345,9 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
Widget _stripeRenewOrCancelButton() {
final bool isRenewCancelled =
_currentSubscription!.attributes?.isCancelled ?? false;
if (isRenewCancelled && _userDetails.hasPaidAddon()) {
return const SizedBox.shrink();
}
final String title = isRenewCancelled
? S.of(context).renewSubscription
: S.of(context).cancelSubscription;
@ -503,7 +511,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
storage: plan.storage,
price: plan.price,
period: plan.period,
isActive: isActive,
isActive: isActive && !_hideCurrentPlanSelection,
),
),
),
@ -594,7 +602,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
storage: _currentSubscription!.storage,
price: _currentSubscription!.price,
period: _currentSubscription!.period,
isActive: true,
isActive: !_hasActiveSubscription,
),
),
),

View file

@ -98,9 +98,14 @@ class ValidityWidget extends StatelessWidget {
if (currentSubscription == null) {
return const SizedBox.shrink();
}
final List<Bonus> addOnBonus = bonusData?.getAddOnBonuses() ?? <Bonus>[];
final bool isFreeTrialSub = currentSubscription!.productID == freeProductID;
if (isFreeTrialSub && (bonusData?.getAddOnBonuses().isNotEmpty ?? false)) {
return const SizedBox.shrink();
bool hideSubValidityView = false;
if (isFreeTrialSub && addOnBonus.isNotEmpty) {
hideSubValidityView = true;
}
if (!currentSubscription!.isValid()) {
hideSubValidityView = true;
}
final endDate =
DateFormat.yMMMd(Localizations.localeOf(context).languageCode).format(
@ -114,11 +119,45 @@ class ValidityWidget extends StatelessWidget {
: S.of(context).freeTrialValidTill(endDate);
} else if (currentSubscription!.attributes?.isCancelled ?? false) {
message = S.of(context).subWillBeCancelledOn(endDate);
if (addOnBonus.isNotEmpty) {
hideSubValidityView = true;
}
}
return Padding(
padding: const EdgeInsets.only(top: 8),
padding: const EdgeInsets.only(top: 0),
child: Column(
children: [
if (!hideSubValidityView)
Text(
message,
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),
if (addOnBonus.isNotEmpty)
...addOnBonus.map((bonus) => AddOnBonusValidity(bonus)).toList(),
],
),
);
}
}
class AddOnBonusValidity extends StatelessWidget {
final Bonus bonus;
const AddOnBonusValidity(this.bonus, {super.key});
@override
Widget build(BuildContext context) {
final endDate =
DateFormat.yMMMd(Localizations.localeOf(context).languageCode).format(
DateTime.fromMicrosecondsSinceEpoch(bonus.validTill),
);
final String storage = convertBytesToReadableFormat(bonus.storage);
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 8),
child: Text(
message,
S.of(context).addOnValidTill(storage, endDate),
style: Theme.of(context).textTheme.bodySmall,
textAlign: TextAlign.center,
),

View file

@ -232,7 +232,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
} catch (e, s) {
_logger.severe("Failed to updated backup folder", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}

View file

@ -94,7 +94,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
try {
status = await SyncService.instance.getBackupStatus();
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return;
}
@ -128,7 +128,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
duplicates =
await DeduplicationService.instance.getDuplicateFiles();
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return;
}

View file

@ -89,7 +89,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
try {
recoveryKey = await _getOrCreateRecoveryKey(context);
} catch (e) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
return;
}
unawaited(
@ -260,14 +260,22 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
await UserService.instance.getOrCreateRecoveryKey(context),
);
}
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(onPasswordVerified: (Uint8List keyEncryptionKey) async {
final Uint8List loginKey = await CryptoUtil.deriveLoginKey(keyEncryptionKey);
await UserService.instance.registerOrUpdateSrp(loginKey);
},),);
final UserDetails details =
await UserService.instance.getUserDetailsV2(memoryCount: false);
if ((details.profileData?.canDisableEmailMFA ?? false) == false) {
await routeToPage(
context,
RequestPasswordVerificationPage(
onPasswordVerified: (Uint8List keyEncryptionKey) async {
final Uint8List loginKey =
await CryptoUtil.deriveLoginKey(keyEncryptionKey);
await UserService.instance.registerOrUpdateSrp(loginKey);
},
),
);
}
await UserService.instance.updateEmailMFA(isEnabled);
} catch (e) {

View file

@ -27,9 +27,7 @@ import 'package:photos/ui/settings/storage_card_widget.dart';
import 'package:photos/ui/settings/support_section_widget.dart';
import 'package:photos/ui/settings/theme_switch_widget.dart';
import "package:photos/ui/sharing/verify_identity_dialog.dart";
import "package:photos/utils/black_friday_util.dart";
import "package:photos/utils/navigation_util.dart";
import "package:url_launcher/url_launcher_string.dart";
class SettingsPage extends StatelessWidget {
final ValueNotifier<String?> emailNotifier;
@ -86,42 +84,25 @@ class SettingsPage extends StatelessWidget {
const sectionSpacing = SizedBox(height: 8);
contents.add(const SizedBox(height: 8));
if (hasLoggedIn) {
final shouldShowBFBanner = shouldShowBfBanner();
final showStorageBonusBanner =
StorageBonusService.instance.shouldShowStorageBonus();
contents.addAll([
const StorageCardWidget(),
(shouldShowBFBanner || showStorageBonusBanner)
(showStorageBonusBanner)
? RepaintBoundary(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: shouldShowBFBanner
? NotificationWidget(
isBlackFriday: true,
startIcon: Icons.celebration,
actionIcon: Icons.arrow_forward_outlined,
text: S.of(context).blackFridaySale,
subText: S.of(context).upto50OffUntil4thDec,
type: NotificationType.goldenBanner,
onTap: () async {
launchUrlString(
"https://ente.io/blackfriday",
mode: LaunchMode.platformDefault,
);
},
)
: NotificationWidget(
startIcon: Icons.auto_awesome,
actionIcon: Icons.arrow_forward_outlined,
text: S.of(context).doubleYourStorage,
subText: S.of(context).referFriendsAnd2xYourPlan,
type: NotificationType.goldenBanner,
onTap: () async {
StorageBonusService.instance
.markStorageBonusAsDone();
routeToPage(context, const ReferralScreen());
},
),
child: NotificationWidget(
startIcon: Icons.auto_awesome,
actionIcon: Icons.arrow_forward_outlined,
text: S.of(context).doubleYourStorage,
subText: S.of(context).referFriendsAnd2xYourPlan,
type: NotificationType.goldenBanner,
onTap: () async {
StorageBonusService.instance.markStorageBonusAsDone();
routeToPage(context, const ReferralScreen());
},
),
).animate(onPlay: (controller) => controller.repeat()).shimmer(
duration: 1000.ms,
delay: 3200.ms,

View file

@ -132,7 +132,7 @@ class _ManageIndividualParticipantState
CollectionParticipantRole.viewer,
);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
if (isConvertToViewSuccess && mounted) {
// reset value

View file

@ -347,7 +347,7 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
}
} catch (e) {
await dialog?.hide();
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
rethrow;
}
}

View file

@ -137,7 +137,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
try {
await CollectionsService.instance.updateShareUrl(widget.collection, prop);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
rethrow;
}
}

View file

@ -180,7 +180,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
try {
await CollectionsService.instance.updateShareUrl(widget.collection, prop);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
rethrow;
}
}

View file

@ -19,6 +19,7 @@ import "package:photos/ui/components/models/button_type.dart";
import 'package:photos/ui/tabs/section_title.dart';
import "package:photos/ui/tabs/shared/empty_state.dart";
import "package:photos/ui/tabs/shared/quick_link_album_item.dart";
import "package:photos/utils/debouncer.dart";
import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/share_util.dart";
@ -36,18 +37,31 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
late StreamSubscription<CollectionUpdatedEvent>
_collectionUpdatesSubscription;
late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
@override
void initState() {
super.initState();
_localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
debugPrint("SetState Shared Collections on ${event.reason}");
setState(() {});
_debouncer.run(() async {
if (mounted) {
debugPrint("SetState Shared Collections on ${event.reason}");
setState(() {});
}
});
});
_collectionUpdatesSubscription =
Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
setState(() {});
_debouncer.run(() async {
if (mounted) {
debugPrint("SetState Shared Collections on ${event.reason}");
setState(() {});
}
});
});
_loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) {
setState(() {});
@ -262,6 +276,7 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
_localFilesSubscription.cancel();
_collectionUpdatesSubscription.cancel();
_loggedOutEvent.cancel();
_debouncer.cancelDebounce();
super.dispose();
}

View file

@ -24,6 +24,7 @@ import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/tabs/section_title.dart";
import "package:photos/ui/viewer/actions/delete_empty_albums.dart";
import "package:photos/ui/viewer/gallery/empty_state.dart";
import "package:photos/utils/debouncer.dart";
import 'package:photos/utils/local_settings.dart';
import "package:photos/utils/navigation_util.dart";
@ -44,19 +45,31 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
AlbumSortKey? sortKey;
String _loadReason = "init";
final _scrollController = ScrollController();
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
static const int _kOnEnteItemLimitCount = 10;
@override
void initState() {
_localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason;
setState(() {});
_debouncer.run(() async {
if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
});
_collectionUpdatesSubscription =
Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
_loadReason = event.reason;
setState(() {});
_debouncer.run(() async {
if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
});
_loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) {
_loadReason = event.reason;
@ -268,6 +281,7 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
_collectionUpdatesSubscription.cancel();
_loggedOutEvent.cancel();
_scrollController.dispose();
_debouncer.cancelDebounce();
super.dispose();
}

View file

@ -1,6 +1,8 @@
import "dart:io";
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/l10n/l10n.dart";
import 'package:photos/ui/common/gradient_button.dart';
import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/utils/auth_util.dart';
@ -21,10 +23,16 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
@override
void initState() {
_logger.info("initState");
_logger.info("initiatingState");
super.initState();
_showLockScreen(source: "initState");
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (isNonMobileIOSDevice()) {
_logger.info('ignore init for non mobile iOS device');
return;
}
_showLockScreen(source: "postFrameInit");
});
}
@override
@ -45,7 +53,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
SizedBox(
width: 180,
child: GradientButton(
text: S.of(context).unlock,
text: context.l10n.unlock,
iconData: Icons.lock_open_outlined,
onTap: () async {
_showLockScreen(source: "tapUnlock");
@ -60,6 +68,14 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
);
}
bool isNonMobileIOSDevice() {
if (Platform.isAndroid) {
return false;
}
var shortestSide = MediaQuery.of(context).size.shortestSide;
return shortestSide > 600 ? true : false;
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
_logger.info(state.toString());
@ -73,7 +89,10 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
if (!_hasAuthenticationFailed && !didAuthInLast5Seconds) {
// Show the lock screen again only if the app is resuming from the
// background, and not when the lock screen was explicitly dismissed
_showLockScreen(source: "lifeCycle");
Future.delayed(
Duration.zero,
() => _showLockScreen(source: "lifeCycle"),
);
} else {
_hasAuthenticationFailed = false; // Reset failure state
}
@ -90,6 +109,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
@override
void dispose() {
_logger.info('disposing');
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@ -101,7 +121,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
_isShowingLockScreen = true;
final result = await requestAuthentication(
context,
S.of(context).authToViewYourMemories,
context.l10n.authToViewYourMemories,
);
_logger.finest("LockScreen Result $result $id");
_isShowingLockScreen = false;
@ -117,6 +137,7 @@ class _LockScreenState extends State<LockScreen> with WidgetsBindingObserver {
}
}
} catch (e, s) {
_isShowingLockScreen = false;
_logger.severe(e, s);
}
}

View file

@ -73,7 +73,6 @@ class DetailPage extends StatefulWidget {
class _DetailPageState extends State<DetailPage> {
static const kLoadLimit = 100;
final _logger = Logger("DetailPageState");
bool _shouldDisableScroll = false;
List<EnteFile>? _files;
late PageController _pageController;
final _selectedIndexNotifier = ValueNotifier(0);
@ -172,14 +171,6 @@ class _DetailPageState extends State<DetailPage> {
file,
autoPlay: shouldAutoPlay(),
tagPrefix: widget.config.tagPrefix,
shouldDisableScroll: (value) {
if (_shouldDisableScroll != value) {
setState(() {
_logger.fine('setState $_shouldDisableScroll to $value');
_shouldDisableScroll = value;
});
}
},
playbackCallback: (isPlaying) {
Future.delayed(Duration.zero, () {
_toggleFullScreen(shouldEnable: isPlaying);
@ -208,9 +199,7 @@ class _DetailPageState extends State<DetailPage> {
}
_preloadEntries();
},
physics: _shouldDisableScroll
? const NeverScrollableScrollPhysics()
: const FastScrollPhysics(speedFactor: 4.0),
physics: const FastScrollPhysics(speedFactor: 4.0),
controller: _pageController,
itemCount: _files!.length,
);

View file

@ -279,7 +279,7 @@ class FileAppBarState extends State<FileAppBar> {
}
} catch (e, s) {
_logger.severe("failed to update file visibility", e, s);
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
}
}
@ -362,7 +362,7 @@ class FileAppBarState extends State<FileAppBar> {
} catch (e) {
_logger.warning("Failed to save file", e);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
} finally {
PhotoManager.startChangeNotify();
LocalSyncService.instance.checkAndSync().ignore();
@ -423,7 +423,7 @@ class FileAppBarState extends State<FileAppBar> {
} catch (e) {
dialog.hide();
_logger.severe("Failed to use as", e);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
}

View file

@ -8,7 +8,6 @@ import "package:photos/ui/viewer/file/zoomable_live_image_new.dart";
class FileWidget extends StatelessWidget {
final EnteFile file;
final String? tagPrefix;
final Function(bool)? shouldDisableScroll;
final Function(bool)? playbackCallback;
final BoxDecoration? backgroundDecoration;
final bool? autoPlay;
@ -16,7 +15,6 @@ class FileWidget extends StatelessWidget {
const FileWidget(
this.file, {
this.autoPlay,
this.shouldDisableScroll,
this.playbackCallback,
this.tagPrefix,
this.backgroundDecoration,
@ -32,7 +30,6 @@ class FileWidget extends StatelessWidget {
file.fileType == FileType.image) {
return ZoomableLiveImageNew(
file,
shouldDisableScroll: shouldDisableScroll,
tagPrefix: tagPrefix,
backgroundDecoration: backgroundDecoration,
key: key ?? ValueKey(fileKey),

View file

@ -26,7 +26,6 @@ import "package:photos/utils/toast_util.dart";
class ZoomableImage extends StatefulWidget {
final EnteFile photo;
final Function(bool)? shouldDisableScroll;
final String? tagPrefix;
final Decoration? backgroundDecoration;
final bool shouldCover;
@ -34,7 +33,6 @@ class ZoomableImage extends StatefulWidget {
const ZoomableImage(
this.photo, {
Key? key,
this.shouldDisableScroll,
required this.tagPrefix,
this.backgroundDecoration,
this.shouldCover = false,
@ -54,9 +52,9 @@ class _ZoomableImageState extends State<ZoomableImage>
bool _loadedLargeThumbnail = false;
bool _loadingFinalImage = false;
bool _loadedFinalImage = false;
ValueChanged<PhotoViewScaleState>? _scaleStateChangedCallback;
bool _isZooming = false;
PhotoViewController _photoViewController = PhotoViewController();
bool _isZooming = false;
ValueChanged<PhotoViewScaleState>? _scaleStateChangedCallback;
@override
void initState() {
@ -64,9 +62,6 @@ class _ZoomableImageState extends State<ZoomableImage>
_logger = Logger("ZoomableImage");
_logger.info('initState for ${_photo.generatedID} with tag ${_photo.tag}');
_scaleStateChangedCallback = (value) {
if (widget.shouldDisableScroll != null) {
widget.shouldDisableScroll!(value != PhotoViewScaleState.initial);
}
_isZooming = value != PhotoViewScaleState.initial;
debugPrint("isZooming = $_isZooming, currentState $value");
};
@ -110,7 +105,6 @@ class _ZoomableImageState extends State<ZoomableImage>
} else {
content = const EnteLoadingWidget();
}
final GestureDragUpdateCallback? verticalDragCallback = _isZooming
? null
: (d) => {
@ -126,6 +120,7 @@ class _ZoomableImageState extends State<ZoomableImage>
},
},
};
return GestureDetector(
onVerticalDragUpdate: verticalDragCallback,
child: content,
@ -266,15 +261,13 @@ class _ZoomableImageState extends State<ZoomableImage>
required ImageProvider? previewImageProvider,
required ImageProvider finalImageProvider,
}) async {
final bool shouldFixPosition = previewImageProvider != null &&
_isZooming &&
_photoViewController.scale != null;
final bool shouldFixPosition = previewImageProvider != null && _isZooming;
ImageInfo? finalImageInfo;
if (shouldFixPosition) {
if (kDebugMode) {
showToast(
context,
'Updating photo scale zooming: $_isZooming and scale: ${_photoViewController.scale}',
'Updating photo scale zooming and scale: ${_photoViewController.scale}',
);
}
final prevImageInfo = await getImageInfo(previewImageProvider);

View file

@ -16,14 +16,12 @@ import 'package:video_player/video_player.dart';
class ZoomableLiveImage extends StatefulWidget {
final EnteFile enteFile;
final Function(bool)? shouldDisableScroll;
final String? tagPrefix;
final Decoration? backgroundDecoration;
const ZoomableLiveImage(
this.enteFile, {
Key? key,
this.shouldDisableScroll,
required this.tagPrefix,
this.backgroundDecoration,
}) : super(key: key);
@ -45,8 +43,9 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
@override
void initState() {
_enteFile = widget.enteFile;
_logger.info('initState for ${_enteFile.generatedID} with tag ${_enteFile
.tag} and name ${_enteFile.displayName}');
_logger.info(
'initState for ${_enteFile.generatedID} with tag ${_enteFile.tag} and name ${_enteFile.displayName}',
);
super.initState();
}
@ -76,7 +75,6 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
content = ZoomableImage(
_enteFile,
tagPrefix: widget.tagPrefix,
shouldDisableScroll: widget.shouldDisableScroll,
backgroundDecoration: widget.backgroundDecoration,
);
}
@ -138,7 +136,8 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
}
Future<File?> _getLivePhotoVideo() async {
if (_enteFile.isRemoteFile && !(await isFileCached(_enteFile, liveVideo: true))) {
if (_enteFile.isRemoteFile &&
!(await isFileCached(_enteFile, liveVideo: true))) {
showShortToast(context, S.of(context).downloading);
}
@ -206,5 +205,4 @@ class _ZoomableLiveImageState extends State<ZoomableLiveImage>
}
});
}
}

View file

@ -16,14 +16,12 @@ import 'package:photos/utils/toast_util.dart';
class ZoomableLiveImageNew extends StatefulWidget {
final EnteFile enteFile;
final Function(bool)? shouldDisableScroll;
final String? tagPrefix;
final Decoration? backgroundDecoration;
const ZoomableLiveImageNew(
this.enteFile, {
Key? key,
this.shouldDisableScroll,
required this.tagPrefix,
this.backgroundDecoration,
}) : super(key: key);
@ -77,7 +75,6 @@ class _ZoomableLiveImageNewState extends State<ZoomableLiveImageNew>
content = ZoomableImage(
_enteFile,
tagPrefix: widget.tagPrefix,
shouldDisableScroll: widget.shouldDisableScroll,
backgroundDecoration: widget.backgroundDecoration,
);
}

View file

@ -31,7 +31,7 @@ class EmptyAlbumState extends StatelessWidget {
try {
await showAddPhotosSheet(context, c);
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
},
),

View file

@ -16,6 +16,7 @@ import "package:photos/ui/viewer/gallery/component/multiple_groups_gallery_view.
import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
import 'package:photos/utils/date_time_util.dart';
import "package:photos/utils/debouncer.dart";
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
typedef GalleryLoader = Future<FileLoadResult> Function(
@ -43,6 +44,8 @@ class Gallery extends StatefulWidget {
final bool enableFileGrouping;
final Widget loadingWidget;
final bool disableScroll;
final Duration reloadDebounceTime;
final Duration reloadDebounceExecutionInterval;
/// When true, selection will be limited to one item. Tapping on any item
/// will select even when no other item is selected.
@ -78,6 +81,8 @@ class Gallery extends StatefulWidget {
this.sortAsyncFn,
this.showSelectAllByDefault = true,
this.isScrollablePositionedList = true,
this.reloadDebounceTime = const Duration(milliseconds: 500),
this.reloadDebounceExecutionInterval = const Duration(seconds: 2),
Key? key,
}) : super(key: key);
@ -89,6 +94,7 @@ class Gallery extends StatefulWidget {
class GalleryState extends State<Gallery> {
static const int kInitialLoadLimit = 100;
late final Debouncer _debouncer;
late Logger _logger;
List<List<EnteFile>> currentGroupedFiles = [];
@ -106,20 +112,26 @@ class GalleryState extends State<Gallery> {
"Gallery_${widget.tagPrefix}${kDebugMode ? "_" + widget.albumName! : ""}";
_logger = Logger(_logTag);
_logger.finest("init Gallery");
_debouncer = Debouncer(
widget.reloadDebounceTime,
executionInterval: widget.reloadDebounceExecutionInterval,
);
_sortOrderAsc = widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
_itemScroller = ItemScrollController();
if (widget.reloadEvent != null) {
_reloadEventSubscription = widget.reloadEvent!.listen((event) async {
// In soft refresh, setState is called for entire gallery only when
// number of child change
_logger.finest("Soft refresh all files on ${event.reason} ");
final result = await _loadFiles();
final bool hasReloaded = _onFilesLoaded(result.files);
if (hasReloaded && kDebugMode) {
_logger.finest(
"Reloaded gallery on soft refresh all files on ${event.reason}",
);
}
_debouncer.run(() async {
// In soft refresh, setState is called for entire gallery only when
// number of child change
_logger.finest("Soft refresh all files on ${event.reason} ");
final result = await _loadFiles();
final bool hasReloaded = _onFilesLoaded(result.files);
if (hasReloaded && kDebugMode) {
_logger.finest(
"Reloaded gallery on soft refresh all files on ${event.reason}",
);
}
});
});
}
_tabDoubleTapEvent =
@ -137,11 +149,13 @@ class GalleryState extends State<Gallery> {
for (final event in widget.forceReloadEvents!) {
_forceReloadEventSubscriptions.add(
event.listen((event) async {
_logger.finest("Force refresh all files on ${event.reason}");
_sortOrderAsc =
widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
final result = await _loadFiles();
_setFilesAndReload(result.files);
_debouncer.run(() async {
_logger.finest("Force refresh all files on ${event.reason}");
_sortOrderAsc =
widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
final result = await _loadFiles();
_setFilesAndReload(result.files);
});
}),
);
}
@ -219,6 +233,7 @@ class GalleryState extends State<Gallery> {
for (final subscription in _forceReloadEventSubscriptions) {
subscription.cancel();
}
_debouncer.cancelDebounce();
super.dispose();
}

View file

@ -172,7 +172,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
},
);
if (result is Exception) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: result);
}
}
@ -204,7 +204,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
);
if (actionResult?.action != null && mounted) {
if (actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: actionResult.exception);
} else if (actionResult.action == ButtonAction.first) {
Navigator.of(context).pop();
}
@ -224,7 +224,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
.getBackupStatus(pathID: widget.deviceCollection!.id);
} catch (e) {
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
return;
}
@ -664,7 +664,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
} catch (e, s) {
_logger.severe("failed to trash collection", e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
} else {
final bool result = await collectionActions.deleteCollectionSheet(
@ -691,7 +691,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
} catch (e, s) {
_logger.severe("failed to trash collection", e, s);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
@ -726,7 +726,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
}
} catch (e, s) {
_logger.severe(e, s);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
@ -736,7 +736,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
await showAddPhotosSheet(bContext, collection!);
} catch (e, s) {
_logger.severe(e, s);
showGenericErrorDialog(context: bContext);
showGenericErrorDialog(context: bContext, error: e);
}
}

View file

@ -118,7 +118,7 @@ class LocationScreenPopUpMenu extends StatelessWidget {
);
Navigator.of(context).pop();
} catch (e) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
}
}
},

View file

@ -200,7 +200,7 @@ class _RadiusPickerWidgetState extends State<RadiusPickerWidget> {
alignMessage: Alignment.centerRight,
);
if (result is Exception) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: result);
_logger.severe(
"Failed to create custom radius",
result,

View file

@ -83,6 +83,8 @@ class _SearchSectionAllPageState extends State<SearchSectionAllPage> {
builder: (context, snapshot) {
if (snapshot.hasData) {
final sectionResults = snapshot.data!;
sectionResults
.sort((a, b) => a.name().compareTo(b.name()));
return Text(sectionResults.length.toString())
.animate()
.fadeIn(

View file

@ -1,21 +0,0 @@
import "dart:io";
import "package:flutter/foundation.dart";
import "package:photos/services/user_service.dart";
bool shouldShowBfBanner() {
if (!Platform.isAndroid && !kDebugMode) {
return false;
}
// if date is after 5th of December 2023, 00:00:00, hide banner
if (DateTime.now().isAfter(DateTime(2023, 12, 5))) {
return false;
}
// if coupon is already applied, can hide the banner
return (UserService.instance
.getCachedUserDetails()
?.bonusData
?.getAddOnBonuses()
.isEmpty ??
true);
}

View file

@ -77,7 +77,7 @@ Future<Uint8List> cryptoGenericHash(Map<String, dynamic> args) async {
EncryptionResult chachaEncryptData(Map<String, dynamic> args) {
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(args["key"]);
final encryptedData = Sodium.cryptoSecretstreamXchacha20poly1305Push(
initPushResult.state,
args["source"],
@ -102,7 +102,7 @@ Future<EncryptionResult> chachaEncryptFile(Map<String, dynamic> args) async {
final inputFile = sourceFile.openSync(mode: FileMode.read);
final key = args["key"] ?? Sodium.cryptoSecretstreamXchacha20poly1305Keygen();
final initPushResult =
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
Sodium.cryptoSecretstreamXchacha20poly1305InitPush(key);
var bytesRead = 0;
var tag = Sodium.cryptoSecretstreamXchacha20poly1305TagMessage;
while (tag != Sodium.cryptoSecretstreamXchacha20poly1305TagFinal) {
@ -156,7 +156,7 @@ Future<void> chachaDecryptFile(Map<String, dynamic> args) async {
final buffer = await inputFile.read(chunkSize);
bytesRead += chunkSize;
final pullResult =
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
Sodium.cryptoSecretstreamXchacha20poly1305Pull(pullState, buffer, null);
await destinationFile.writeAsBytes(pullResult.m, mode: FileMode.append);
tag = pullResult.tag;
}
@ -190,20 +190,22 @@ class CryptoUtil {
Sodium.init();
}
static Uint8List base642bin(String b64, {
static Uint8List base642bin(
String b64, {
String? ignore,
int variant = Sodium.base64VariantOriginal,
}) {
return Sodium.base642bin(b64, ignore: ignore, variant: variant);
}
static String bin2base64(Uint8List bin, {
static String bin2base64(
Uint8List bin, {
bool urlSafe = false,
}) {
return Sodium.bin2base64(
bin,
variant:
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
urlSafe ? Sodium.base64VariantUrlsafe : Sodium.base64VariantOriginal,
);
}
@ -237,9 +239,11 @@ class CryptoUtil {
// Decrypts the given cipher, with the given key and nonce using XSalsa20
// (w Poly1305 MAC).
static Future<Uint8List> decrypt(Uint8List cipher,
Uint8List key,
Uint8List nonce,) async {
static Future<Uint8List> decrypt(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) async {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@ -256,9 +260,11 @@ class CryptoUtil {
// This function runs on the same thread as the caller, so should be used only
// for small amounts of data where thread switching can result in a degraded
// user experience
static Uint8List decryptSync(Uint8List cipher,
Uint8List key,
Uint8List nonce,) {
static Uint8List decryptSync(
Uint8List cipher,
Uint8List key,
Uint8List nonce,
) {
final args = <String, dynamic>{};
args["cipher"] = cipher;
args["nonce"] = nonce;
@ -270,8 +276,10 @@ class CryptoUtil {
// nonce, using XChaCha20 (w Poly1305 MAC).
// This function runs on the isolate pool held by `_computer`.
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<EncryptionResult> encryptChaCha(Uint8List source,
Uint8List key,) async {
static Future<EncryptionResult> encryptChaCha(
Uint8List source,
Uint8List key,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@ -285,9 +293,11 @@ class CryptoUtil {
// Decrypts the given source, with the given key and header using XChaCha20
// (w Poly1305 MAC).
// TODO: Remove "ChaCha", an implementation detail from the function name
static Future<Uint8List> decryptChaCha(Uint8List source,
Uint8List key,
Uint8List header,) async {
static Future<Uint8List> decryptChaCha(
Uint8List source,
Uint8List key,
Uint8List header,
) async {
final args = <String, dynamic>{};
args["source"] = source;
args["key"] = key;
@ -304,10 +314,10 @@ class CryptoUtil {
// to the destinationFilePath.
// If a key is not provided, one is generated and returned.
static Future<EncryptionResult> encryptFile(
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
String sourceFilePath,
String destinationFilePath, {
Uint8List? key,
}) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@ -322,10 +332,11 @@ class CryptoUtil {
// Decrypts the file at sourceFilePath, with the given key and header using
// XChaCha20 (w Poly1305 MAC), and writes it to the destinationFilePath.
static Future<void> decryptFile(
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,) {
String sourceFilePath,
String destinationFilePath,
Uint8List header,
Uint8List key,
) {
final args = <String, dynamic>{};
args["sourceFilePath"] = sourceFilePath;
args["destinationFilePath"] = destinationFilePath;
@ -356,10 +367,10 @@ class CryptoUtil {
// Decrypts the input using the given publicKey-secretKey pair
static Uint8List openSealSync(
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
Uint8List input,
Uint8List publicKey,
Uint8List secretKey,
) {
return Sodium.cryptoBoxSealOpen(input, publicKey, secretKey);
}
@ -377,9 +388,9 @@ class CryptoUtil {
// At all points, we ensure that the product of these two variables (the area
// under the graph that determines the amount of work required) is a constant.
static Future<DerivedKeyResult> deriveSensitiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final logger = Logger("pwhash");
int memLimit = Sodium.cryptoPwhashMemlimitSensitive;
int opsLimit = Sodium.cryptoPwhashOpslimitSensitive;
@ -407,7 +418,10 @@ class CryptoUtil {
return DerivedKeyResult(key, memLimit, opsLimit);
} catch (e, s) {
logger.warning(
"failed to deriveKey mem: $memLimit, ops: $opsLimit", e, s,);
"failed to deriveKey mem: $memLimit, ops: $opsLimit",
e,
s,
);
}
memLimit = (memLimit / 2).round();
opsLimit = opsLimit * 2;
@ -421,9 +435,9 @@ class CryptoUtil {
// extra layer of authentication (atop the access token and collection key).
// More details @ https://ente.io/blog/building-shareable-links/
static Future<DerivedKeyResult> deriveInteractiveKey(
Uint8List password,
Uint8List salt,
) async {
Uint8List password,
Uint8List salt,
) async {
final int memLimit = Sodium.cryptoPwhashMemlimitInteractive;
final int opsLimit = Sodium.cryptoPwhashOpslimitInteractive;
final key = await deriveKey(password, salt, memLimit, opsLimit);
@ -433,23 +447,23 @@ class CryptoUtil {
// Derives a key for a given password, salt, memLimit and opsLimit using
// Argon2id, v1.3.
static Future<Uint8List> deriveKey(
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) {
Uint8List password,
Uint8List salt,
int memLimit,
int opsLimit,
) async {
try {
return _computer.compute(
cryptoPwHash,
param: {
"password": password,
"salt": salt,
"memLimit": memLimit,
"opsLimit": opsLimit,
},
taskName: "deriveKey",
);
} catch(e,s) {
return await _computer.compute(
cryptoPwHash,
param: {
"password": password,
"salt": salt,
"memLimit": memLimit,
"opsLimit": opsLimit,
},
taskName: "deriveKey",
);
} catch (e, s) {
final String errMessage = 'failed to deriveKey memLimit: $memLimit and '
'opsLimit: $opsLimit';
Logger("CryptoUtilDeriveKey").warning(errMessage, e, s);
@ -461,20 +475,25 @@ class CryptoUtil {
// (Key Derivation Function) with the `loginSubKeyId` and
// `loginSubKeyLen` and `loginSubKeyContext` as context
static Future<Uint8List> deriveLoginKey(
Uint8List key,
) async {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
Uint8List key,
) async {
try {
final Uint8List derivedKey = await _computer.compute(
cryptoKdfDeriveFromKey,
param: {
"key": key,
"subkeyId": loginSubKeyId,
"subkeyLen": loginSubKeyLen,
"context": utf8.encode(loginSubKeyContext),
},
taskName: "deriveLoginKey",
);
// return the first 16 bytes of the derived key
return derivedKey.sublist(0, 16);
} catch (e, s) {
Logger("deriveLoginKey").severe("loginKeyDerivation failed", e, s);
throw LoginKeyDerivationError();
}
}
// Computes and returns the hash of the source file

View file

@ -9,9 +9,10 @@ class Debouncer {
///in milliseconds
final ValueNotifier<bool> _debounceActiveNotifier = ValueNotifier(false);
/// If executionInterval is not null, then the debouncer will execute the
/// If executionIntervalInSeconds is not null, then the debouncer will execute the
/// current callback it has in run() method repeatedly in the given interval.
final int? executionInterval;
/// This is useful for example when you want to execute a callback every 5 seconds
final Duration? executionInterval;
Timer? _debounceTimer;
Debouncer(this._duration, {this.executionInterval});
@ -19,14 +20,24 @@ class Debouncer {
final Stopwatch _stopwatch = Stopwatch();
void run(FutureVoidCallback fn) {
bool shouldRunImmediately = false;
if (executionInterval != null) {
runCallbackIfIntervalTimeElapses(fn);
// ensure the stop watch is running
_stopwatch.start();
if (_stopwatch.elapsedMilliseconds > executionInterval!.inMilliseconds) {
shouldRunImmediately = true;
_stopwatch.stop();
_stopwatch.reset();
}
}
if (isActive()) {
_debounceTimer!.cancel();
}
_debounceTimer = Timer(_duration, () async {
_debounceTimer =
Timer(shouldRunImmediately ? Duration.zero : _duration, () async {
_stopwatch.stop();
_stopwatch.reset();
await fn();
_debounceActiveNotifier.value = false;
});
@ -39,14 +50,6 @@ class Debouncer {
}
}
runCallbackIfIntervalTimeElapses(FutureVoidCallback fn) {
_stopwatch.isRunning ? null : _stopwatch.start();
if (_stopwatch.elapsedMilliseconds > executionInterval!) {
_stopwatch.reset();
fn();
}
}
bool isActive() => _debounceTimer != null && _debounceTimer!.isActive;
ValueNotifier<bool> get debounceActiveNotifier {

View file

@ -102,7 +102,7 @@ Future<void> deleteFilesFromEverywhere(
await FilesDB.instance.deleteMultipleUploadedFiles(fileIDs);
} catch (e) {
_logger.severe(e);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
rethrow;
}
for (final collectionID in updatedCollectionIDs) {
@ -163,7 +163,7 @@ Future<void> deleteFilesFromRemoteOnly(
await FilesDB.instance.deleteMultipleUploadedFiles(uploadedFileIDs);
} catch (e, s) {
_logger.severe("Failed to delete files from remote", e, s);
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
rethrow;
}
for (final collectionID in updatedCollectionIDs) {
@ -279,7 +279,10 @@ Future<bool> deleteFromTrash(BuildContext context, List<EnteFile> files) async {
actionResult!.action == ButtonAction.cancel) {
return didDeletionStart ? true : false;
} else if (actionResult.action == ButtonAction.error) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(
context: context,
error: actionResult.exception,
);
return false;
} else {
return true;
@ -306,7 +309,10 @@ Future<bool> emptyTrash(BuildContext context) async {
actionResult!.action == ButtonAction.cancel) {
return false;
} else if (actionResult.action == ButtonAction.error) {
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(
context: context,
error: actionResult.exception,
);
return false;
} else {
return true;
@ -555,7 +561,7 @@ Future<void> showDeleteSheet(
showShortToast(context, S.of(context).movedToTrash);
},
onError: (e, s) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: e);
},
);
},
@ -620,7 +626,7 @@ Future<void> showDeleteSheet(
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: actionResult.exception);
} else {
selectedFiles.clearAll();
}

View file

@ -1,6 +1,8 @@
import "package:dio/dio.dart";
import "package:flutter/foundation.dart";
import 'package:flutter/material.dart';
import "package:flutter/services.dart";
import "package:photos/core/constants.dart";
import "package:photos/generated/l10n.dart";
import 'package:photos/models/button_result.dart';
import 'package:photos/models/typedefs.dart';
@ -11,6 +13,7 @@ import 'package:photos/ui/components/action_sheet_widget.dart';
import 'package:photos/ui/components/buttons/button_widget.dart';
import 'package:photos/ui/components/dialog_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
import "package:photos/utils/email_util.dart";
typedef DialogBuilder = DialogWidget Function(BuildContext context);
@ -68,25 +71,93 @@ Future<ButtonResult?> showErrorDialogForException({
);
}
String parseErrorForUI(
BuildContext context,
String genericError, {
Object? error,
bool surfaceError = kDebugMode,
}) {
if (error == null) {
return genericError;
}
if (error is DioError) {
final DioError dioError = error;
if (dioError.type == DioErrorType.other) {
if (dioError.error.toString().contains('Failed host lookup')) {
return S.of(context).networkHostLookUpErr;
} else if (dioError.error.toString().contains('SocketException')) {
return S.of(context).networkConnectionRefusedErr;
}
}
}
// return generic error if the user is not internal and the error is not in debug mode
if (!(isInternalUser && kDebugMode)) {
return genericError;
}
String errorInfo = "";
if (error is DioError) {
final DioError dioError = error;
if (dioError.type == DioErrorType.response) {
if (dioError.response?.data["code"] != null) {
errorInfo = "Reason: " + dioError.response!.data["code"];
} else {
errorInfo = "Reason: " + dioError.response!.data.toString();
}
} else if (dioError.type == DioErrorType.other) {
errorInfo = "Reason: " + dioError.error.toString();
} else {
errorInfo = "Reason: " + dioError.type.toString();
}
} else {
errorInfo = error.toString().split('Source stack')[0];
}
if (errorInfo.isNotEmpty) {
return "$genericError\n\n$errorInfo";
}
return genericError;
}
///Will return null if dismissed by tapping outside
Future<ButtonResult?> showGenericErrorDialog({
required BuildContext context,
bool isDismissible = true,
required Object? error,
}) async {
return showDialogWidget(
final errorBody = parseErrorForUI(
context,
S.of(context).itLooksLikeSomethingWentWrongPleaseRetryAfterSome,
error: error,
);
final ButtonResult? result = await showDialogWidget(
context: context,
title: S.of(context).error,
icon: Icons.error_outline_outlined,
body: S.of(context).itLooksLikeSomethingWentWrongPleaseRetryAfterSome,
body: errorBody,
isDismissible: isDismissible,
buttons: const [
buttons: [
ButtonWidget(
buttonType: ButtonType.primary,
labelText: S.of(context).ok,
buttonAction: ButtonAction.first,
isInAlert: true,
),
ButtonWidget(
buttonType: ButtonType.secondary,
labelText: "OK",
isInAlert: true,
labelText: S.of(context).contactSupport,
buttonAction: ButtonAction.second,
onTap: () async {
await sendLogs(
context,
S.of(context).contactSupport,
"support@ente.io",
postShare: () {},
);
},
),
],
);
return result;
}
DialogWidget choiceDialog({

View file

@ -129,8 +129,8 @@ Future<MediaUploadData> _getMediaUploadDataFromAssetFile(EnteFile file) async {
_logger.fine("Uploading zipped live photo from " + livePhotoPath);
final encoder = ZipFileEncoder();
encoder.create(livePhotoPath);
encoder.addFile(videoUrl, "video" + extension(videoUrl.path));
encoder.addFile(sourceFile, "image" + extension(sourceFile.path));
await encoder.addFile(videoUrl, "video" + extension(videoUrl.path));
await encoder.addFile(sourceFile, "image" + extension(sourceFile.path));
encoder.close();
// delete the temporary video and image copy (only in IOS)
if (Platform.isIOS) {

View file

@ -218,7 +218,7 @@ Future<void> editFilename(
);
if (result is Exception) {
_logger.severe("Failed to rename file");
showGenericErrorDialog(context: context);
showGenericErrorDialog(context: context, error: result);
}
}

View file

@ -74,7 +74,7 @@ Future<void> share(
s,
);
await dialog.hide();
await showGenericErrorDialog(context: context);
await showGenericErrorDialog(context: context, error: e);
}
}

View file

@ -1194,18 +1194,18 @@ packages:
dependency: "direct main"
description:
name: media_kit
sha256: "3dffc6d0c19117d51fbc42a7f89612e0595665800a596289ab7a80bdd93e0ad1"
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
url: "https://pub.dev"
source: hosted
version: "1.1.9"
version: "1.1.10+1"
media_kit_libs_android_video:
dependency: transitive
description:
name: media_kit_libs_android_video
sha256: a7ef60926ac528e2fabe9ee7084e648e385422a881ba914c978a7a81e6595dee
sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c"
url: "https://pub.dev"
source: hosted
version: "1.3.5"
version: "1.3.6"
media_kit_libs_ios_video:
dependency: transitive
description:
@ -1234,10 +1234,10 @@ packages:
dependency: "direct main"
description:
name: media_kit_libs_video
sha256: f130964bd4c0907d0af645ba03c8080a914776bfd2e23761a5e22ac3c0c0906a
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
version: "1.0.4"
media_kit_libs_windows_video:
dependency: transitive
description:
@ -1258,10 +1258,10 @@ packages:
dependency: "direct main"
description:
name: media_kit_video
sha256: b1a427f0540c5f052dfab73e4b76a5eb8efa7ebb5d83179cb23fc3932afc315a
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.4"
meta:
dependency: transitive
description:
@ -1299,10 +1299,10 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: e4edd3a158c667976660a788e908d1faf5c5a4f8
resolved-ref: f1ec3d35d5cee2ec1c098b5ca276f311a96d800a
url: "https://github.com/ente-io/motion_photo.git"
source: git
version: "0.0.5"
version: "0.0.6"
motionphoto:
dependency: "direct main"
description:

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.8.0+520
version: 0.8.9+529
environment:
sdk: ">=3.0.0 <4.0.0"
@ -101,9 +101,9 @@ dependencies:
logging: ^1.0.1
lottie: ^1.2.2
media_extension: ^1.0.1
media_kit: ^1.1.9
media_kit_libs_video: ^1.0.3
media_kit_video: ^1.2.1
media_kit: ^1.1.10+1
media_kit_libs_video: ^1.0.4
media_kit_video: ^1.2.4
modal_bottom_sheet: ^3.0.0-pre
motion_photos:
git: "https://github.com/ente-io/motion_photo.git"