diff --git a/mobile/android/app/src/main/res/drawable-hdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-hdpi/android12splash.png index bfdc42157d..153b05b109 100644 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-hdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-hdpi/splash.png b/mobile/android/app/src/main/res/drawable-hdpi/splash.png index bfdc42157d..153b05b109 100644 Binary files a/mobile/android/app/src/main/res/drawable-hdpi/splash.png and b/mobile/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-mdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-mdpi/android12splash.png index 3911d58c35..358b349d23 100644 Binary files a/mobile/android/app/src/main/res/drawable-mdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-mdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-mdpi/splash.png b/mobile/android/app/src/main/res/drawable-mdpi/splash.png index 3911d58c35..358b349d23 100644 Binary files a/mobile/android/app/src/main/res/drawable-mdpi/splash.png and b/mobile/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png index bfdc42157d..153b05b109 100644 Binary files a/mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png index 3911d58c35..358b349d23 100644 Binary files a/mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png index 3d26dfd83c..5bd6530afa 100644 Binary files a/mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png index e9641f3f28..86ec272c98 100644 Binary files a/mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png index 254e7716e5..f467da24d2 100644 Binary files a/mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png index 3d26dfd83c..5bd6530afa 100644 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/splash.png b/mobile/android/app/src/main/res/drawable-xhdpi/splash.png index 3d26dfd83c..5bd6530afa 100644 Binary files a/mobile/android/app/src/main/res/drawable-xhdpi/splash.png and b/mobile/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png index e9641f3f28..86ec272c98 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png b/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png index e9641f3f28..86ec272c98 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png and b/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png index 254e7716e5..f467da24d2 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png and b/mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png b/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png index 254e7716e5..f467da24d2 100644 Binary files a/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png and b/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/mobile/android/app/src/main/res/drawable/notification_icon.png b/mobile/android/app/src/main/res/drawable/notification_icon.png index dbaecfac8d..fc0086775a 100644 Binary files a/mobile/android/app/src/main/res/drawable/notification_icon.png and b/mobile/android/app/src/main/res/drawable/notification_icon.png differ diff --git a/mobile/assets/splash-screen-icon.png b/mobile/assets/splash-screen-icon.png index 5857817eac..5ccda51ae9 100644 Binary files a/mobile/assets/splash-screen-icon.png and b/mobile/assets/splash-screen-icon.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-1024x1024@1x.png deleted file mode 100644 index e6bae73ea5..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@1x.png deleted file mode 100644 index bb28b6a74f..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@2x.png deleted file mode 100644 index edb8f95896..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@3x.png deleted file mode 100644 index a713469b6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@1x.png deleted file mode 100644 index cc98c8dbf8..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@2x.png deleted file mode 100644 index 119692c5d1..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@3x.png deleted file mode 100644 index ed6d5a382b..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@1x.png deleted file mode 100644 index edb8f95896..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@2x.png deleted file mode 100644 index 10e0242d59..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@3x.png deleted file mode 100644 index aea4e77fc3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@1x.png deleted file mode 100644 index ef16e5d8d3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@2x.png deleted file mode 100644 index 65890b669d..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-50x50@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@1x.png deleted file mode 100644 index 8ee523aabb..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@2x.png deleted file mode 100644 index 2294b68ed0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-57x57@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@2x.png deleted file mode 100644 index aea4e77fc3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@3x.png deleted file mode 100644 index 5d4fec318f..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@1x.png deleted file mode 100644 index 730f69093a..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@2x.png deleted file mode 100644 index 6d804c6329..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-72x72@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@1x.png deleted file mode 100644 index 12bf9e7022..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@2x.png deleted file mode 100644 index fd483027e7..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-83.5x83.5@2x.png deleted file mode 100644 index ab2316f7d1..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/AppIcon-dev-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json deleted file mode 100644 index 624ea89d6b..0000000000 --- a/mobile/ios/Runner/Assets.xcassets/AppIcon-dev.appiconset/Contents.json +++ /dev/null @@ -1 +0,0 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"AppIcon-dev-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"AppIcon-dev-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-dev-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-dev-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"AppIcon-dev-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-dev-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"AppIcon-dev-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-dev-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"AppIcon-dev-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-dev-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"AppIcon-dev-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-dev-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"AppIcon-dev-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-dev-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"AppIcon-dev-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-dev-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"AppIcon-dev-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-dev-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"AppIcon-dev-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-dev-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"AppIcon-dev-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-dev-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"AppIcon-dev-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"AppIcon-dev-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppIcon-dev-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d36b1fab2d..0000000000 --- a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "Icon-App-1024x1024@1x.png", - "scale" : "1x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png deleted file mode 100644 index dae37bd184..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 96121f23e3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 01c27d8e6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index 40900845dc..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 1817589a70..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 0e7b036ecd..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index adc33edcf0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 01c27d8e6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index 6d9a9a00ce..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index dac316a97e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index dac316a97e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 9b3595b796..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index 8c9f7e31cd..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 5f0e507c6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 5ef5ee48c0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/Contents.json b/mobile/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/mobile/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json index 396f679201..d8f8ecfb6f 100644 --- a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json @@ -1 +1,38 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"IconDark-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconDark-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconDark-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconDark-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconDark-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconDark-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconDark-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"IconDark-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconDark-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"IconDark-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconDark-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconDark-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconDark-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconDark-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconDark-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconDark-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconDark-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconDark-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconDark-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconDark-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconDark-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconDark-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconDark-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconDark-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconDark-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images" : [ + { + "filename" : "IconDarkAny.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "IconDarkDark.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "IconDarkTinted.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-1024x1024@1x.png deleted file mode 100644 index 67ac1a11b6..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@1x.png deleted file mode 100644 index f9b8d4ec14..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@2x.png deleted file mode 100644 index da8733853f..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@3x.png deleted file mode 100644 index aaf5b943ad..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@1x.png deleted file mode 100644 index f0f89fefc9..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@2x.png deleted file mode 100644 index edaef5ec5d..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@3x.png deleted file mode 100644 index 7c2f28ca68..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@1x.png deleted file mode 100644 index da8733853f..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@2x.png deleted file mode 100644 index 904cb38279..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@3x.png deleted file mode 100644 index e016412765..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@1x.png deleted file mode 100644 index 487c1cdb1b..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@2x.png deleted file mode 100644 index 6b51e9c167..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-50x50@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@1x.png deleted file mode 100644 index b3bd3e807b..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@2x.png deleted file mode 100644 index a6199d9564..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-57x57@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@2x.png deleted file mode 100644 index e016412765..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@3x.png deleted file mode 100644 index 0ac53878e7..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@1x.png deleted file mode 100644 index 7adf5f5915..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@2x.png deleted file mode 100644 index 72db153a35..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-72x72@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@1x.png deleted file mode 100644 index de6b7a2bd6..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@2x.png deleted file mode 100644 index f50bb58f46..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-83.5x83.5@2x.png deleted file mode 100644 index 5c67dfff7f..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDark-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png new file mode 100644 index 0000000000..5ce8a224d7 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png new file mode 100644 index 0000000000..5ce8a224d7 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png new file mode 100644 index 0000000000..3c561bf11e Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json index f77b2bd70a..839660a4c8 100644 --- a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json @@ -1 +1,38 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"IconGreen-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconGreen-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconGreen-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconGreen-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconGreen-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"IconGreen-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconGreen-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"IconGreen-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconGreen-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconGreen-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconGreen-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconGreen-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconGreen-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconGreen-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconGreen-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconGreen-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconGreen-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconGreen-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconGreen-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconGreen-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconGreen-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconGreen-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconGreen-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images" : [ + { + "filename" : "IconGreenAny.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "IconGreenDark.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "IconGreenTinted.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-1024x1024@1x.png deleted file mode 100644 index 2f65011626..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@1x.png deleted file mode 100644 index 16077fe01c..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@2x.png deleted file mode 100644 index 031d4b89d2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@3x.png deleted file mode 100644 index ca83c2170a..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@1x.png deleted file mode 100644 index b3f51ae134..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@2x.png deleted file mode 100644 index 1814cff481..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@3x.png deleted file mode 100644 index a143d3bf42..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@1x.png deleted file mode 100644 index 031d4b89d2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@2x.png deleted file mode 100644 index 7f0f4aaf77..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@3x.png deleted file mode 100644 index 4eb408919d..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@1x.png deleted file mode 100644 index c80c05206d..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@2x.png deleted file mode 100644 index d08253d8e3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-50x50@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@1x.png deleted file mode 100644 index c5ba38bfc3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@2x.png deleted file mode 100644 index 63ffbaf455..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-57x57@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@2x.png deleted file mode 100644 index 4eb408919d..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@3x.png deleted file mode 100644 index 195599a726..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@1x.png deleted file mode 100644 index b142919718..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@2x.png deleted file mode 100644 index cbfe3e99ea..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-72x72@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@1x.png deleted file mode 100644 index 11407f37db..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@2x.png deleted file mode 100644 index 2185570d0e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-83.5x83.5@2x.png deleted file mode 100644 index fa0a47e5e0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreen-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png new file mode 100644 index 0000000000..2141faf134 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png new file mode 100644 index 0000000000..5ce8a224d7 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png new file mode 100644 index 0000000000..3c561bf11e Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json index 7f6b53bb55..fbd4b126bd 100644 --- a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json @@ -1 +1,38 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"IconLight-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconLight-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconLight-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconLight-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconLight-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconLight-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconLight-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"IconLight-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconLight-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"IconLight-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconLight-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconLight-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconLight-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconLight-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconLight-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconLight-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconLight-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconLight-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconLight-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconLight-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconLight-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconLight-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconLight-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconLight-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconLight-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images" : [ + { + "filename" : "IconLightAny.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "IconLightDark.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "IconLightTinted.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-1024x1024@1x.png deleted file mode 100644 index 9e237712a2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@1x.png deleted file mode 100644 index 0278077e70..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@2x.png deleted file mode 100644 index a1334bd91b..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@3x.png deleted file mode 100644 index c362988466..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@1x.png deleted file mode 100644 index 23fa3efda3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@2x.png deleted file mode 100644 index f64a394849..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@3x.png deleted file mode 100644 index 42d10cb046..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@1x.png deleted file mode 100644 index a1334bd91b..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@2x.png deleted file mode 100644 index 7c9059f853..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@3x.png deleted file mode 100644 index 30ab15b65c..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@1x.png deleted file mode 100644 index ff599f74be..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@2x.png deleted file mode 100644 index 1fa7cf8532..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-50x50@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@1x.png deleted file mode 100644 index cc4bafafff..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@2x.png deleted file mode 100644 index ee5d8f261e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-57x57@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@2x.png deleted file mode 100644 index 30ab15b65c..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@3x.png deleted file mode 100644 index 8025b5dfff..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@1x.png deleted file mode 100644 index 23f492ceb2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@2x.png deleted file mode 100644 index 818e285071..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-72x72@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@1x.png deleted file mode 100644 index f82f98e196..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@2x.png deleted file mode 100644 index 96e3b45fb2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-83.5x83.5@2x.png deleted file mode 100644 index 44acc1f155..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLight-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png new file mode 100644 index 0000000000..5ca195643b Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png new file mode 100644 index 0000000000..5ce8a224d7 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png new file mode 100644 index 0000000000..3c561bf11e Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json index 0284920359..28ea4fb2e9 100644 --- a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json +++ b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json @@ -1 +1,38 @@ -{"images":[{"size":"20x20","idiom":"iphone","filename":"IconOG-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"IconOG-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"IconOG-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"IconOG-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"IconOG-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"IconOG-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"IconOG-40x40@3x.png","scale":"3x"},{"size":"50x50","idiom":"ipad","filename":"IconOG-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"IconOG-50x50@2x.png","scale":"2x"},{"size":"57x57","idiom":"iphone","filename":"IconOG-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"IconOG-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconOG-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"IconOG-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"IconOG-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"IconOG-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"IconOG-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"IconOG-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"IconOG-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"IconOG-40x40@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"IconOG-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"IconOG-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"IconOG-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"IconOG-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"IconOG-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"IconOG-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}} \ No newline at end of file +{ + "images" : [ + { + "filename" : "IconOGAny.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "IconOGDark.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "filename" : "IconOGTinted.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-1024x1024@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-1024x1024@1x.png deleted file mode 100644 index dae37bd184..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-1024x1024@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@1x.png deleted file mode 100644 index 96121f23e3..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@2x.png deleted file mode 100644 index 01c27d8e6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@3x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@3x.png deleted file mode 100644 index 40900845dc..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-20x20@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@1x.png deleted file mode 100644 index 1817589a70..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@2x.png deleted file mode 100644 index 0e7b036ecd..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@3x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@3x.png deleted file mode 100644 index adc33edcf0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-29x29@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@1x.png deleted file mode 100644 index 01c27d8e6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@2x.png deleted file mode 100644 index 6d9a9a00ce..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@3x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@3x.png deleted file mode 100644 index dac316a97e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-40x40@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@1x.png deleted file mode 100644 index 3c0a425b76..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@2x.png deleted file mode 100644 index 2ac2f4cfd8..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-50x50@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@1x.png deleted file mode 100644 index fa74d3904c..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@2x.png deleted file mode 100644 index 24556e75e7..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-57x57@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@2x.png deleted file mode 100644 index dac316a97e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@3x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@3x.png deleted file mode 100644 index 9b3595b796..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-60x60@3x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@1x.png deleted file mode 100644 index a76fbefc65..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@2x.png deleted file mode 100644 index f9653a30a2..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-72x72@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@1x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@1x.png deleted file mode 100644 index 8c9f7e31cd..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@1x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@2x.png deleted file mode 100644 index 5f0e507c6e..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-76x76@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-83.5x83.5@2x.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-83.5x83.5@2x.png deleted file mode 100644 index 5ef5ee48c0..0000000000 Binary files a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOG-83.5x83.5@2x.png and /dev/null differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png new file mode 100644 index 0000000000..7a0504b429 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png new file mode 100644 index 0000000000..9bc5fb6721 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGtinted.png b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGtinted.png new file mode 100644 index 0000000000..9bc5fb6721 Binary files /dev/null and b/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGtinted.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png index 3911d58c35..358b349d23 100644 Binary files a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png index 3d26dfd83c..5bd6530afa 100644 Binary files a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png index e9641f3f28..86ec272c98 100644 Binary files a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png and b/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/mobile/lib/generated/intl/messages_ar.dart b/mobile/lib/generated/intl/messages_ar.dart index be85a7dc78..3ef715f7a2 100644 --- a/mobile/lib/generated/intl/messages_ar.dart +++ b/mobile/lib/generated/intl/messages_ar.dart @@ -74,10 +74,14 @@ class MessageLookup extends MessageLookupByLibrary { "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), "sorry": MessageLookupByLibrary.simpleMessage("المعذرة"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "terminate": MessageLookupByLibrary.simpleMessage("؄نهاؔ"), "terminateSession": MessageLookupByLibrary.simpleMessage("؄نهاؔ Ų§Ł„Ų¬Ł„Ų³Ų©ŲŸ"), "thisDevice": MessageLookupByLibrary.simpleMessage("هذا الجهاز"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "سيؤدي هذا ؄لى ŲŖŲ³Ų¬ŁŠŁ„ خروجك من الجهاز Ų§Ł„ŲŖŲ§Ł„ŁŠ:"), diff --git a/mobile/lib/generated/intl/messages_be.dart b/mobile/lib/generated/intl/messages_be.dart index 5a381a58b0..1c1debfec2 100644 --- a/mobile/lib/generated/intl/messages_be.dart +++ b/mobile/lib/generated/intl/messages_be.dart @@ -267,6 +267,8 @@ class MessageLookup extends MessageLookupByLibrary { "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": MessageLookupByLibrary.simpleMessage( "ŠŠµŠ¼Š°Š³Ń‡Ń‹Š¼Š° Š·Š³ŠµŠ½ŠµŃ€Ń‹Ń€Š°Š²Š°Ń†ŃŒ ŠŗŠ»ŃŽŃ‡Ń‹ Š±ŃŃŠæŠµŠŗŃ– на Š³ŃŃ‚ай прылаГзе.\n\nŠ—Š°Ń€ŃŠ³Ń–ŃŃ‚Ń€ŃƒŠ¹Ń†ŠµŃŃ Š· Ń–Š½ŃˆŠ°Š¹ прылаГы."), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "status": MessageLookupByLibrary.simpleMessage("Дтан"), "storageInGB": m2, "strongStrength": MessageLookupByLibrary.simpleMessage("ŠŠ°Š“Š·ŠµŠ¹Š½Ń‹"), @@ -280,6 +282,8 @@ class MessageLookup extends MessageLookupByLibrary { "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Умовы"), "theme": MessageLookupByLibrary.simpleMessage("Тема"), "thisDevice": MessageLookupByLibrary.simpleMessage("Š“ŃŃ‚Š° прылаГа"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Š“ŃŃ‚Š° Š“Š·ŠµŃŠ½Š½Šµ Š·Š°Š²ŃŃ€ŃˆŃ‹Ń†ŃŒ сеанс на Š½Š°ŃŃ‚ŃƒŠæŠ½Š°Š¹ прылаГзе:"), diff --git a/mobile/lib/generated/intl/messages_bg.dart b/mobile/lib/generated/intl/messages_bg.dart index a9ba192553..209a49fb30 100644 --- a/mobile/lib/generated/intl/messages_bg.dart +++ b/mobile/lib/generated/intl/messages_bg.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_ca.dart b/mobile/lib/generated/intl/messages_ca.dart index 84bd2f9cee..de87b53ea9 100644 --- a/mobile/lib/generated/intl/messages_ca.dart +++ b/mobile/lib/generated/intl/messages_ca.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_cs.dart b/mobile/lib/generated/intl/messages_cs.dart index a37e950e18..5ac2e0935c 100644 --- a/mobile/lib/generated/intl/messages_cs.dart +++ b/mobile/lib/generated/intl/messages_cs.dart @@ -55,6 +55,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_da.dart b/mobile/lib/generated/intl/messages_da.dart index 643e07680f..b5f62965a7 100644 --- a/mobile/lib/generated/intl/messages_da.dart +++ b/mobile/lib/generated/intl/messages_da.dart @@ -337,6 +337,8 @@ class MessageLookup extends MessageLookupByLibrary { "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": MessageLookupByLibrary.simpleMessage( "Beklager, vi kunne ikke generere sikre krypteringsnĆøgler pĆ„ denne enhed.\n\nForsĆøg venligst at oprette en konto fra en anden enhed."), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "status": MessageLookupByLibrary.simpleMessage("Status"), "storageInGB": m2, "strongStrength": MessageLookupByLibrary.simpleMessage("StƦrkt"), @@ -351,6 +353,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Betingelser"), "theyAlsoGetXGb": m9, "thisDevice": MessageLookupByLibrary.simpleMessage("Denne enhed"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Dette vil logge dig ud af fĆølgende enhed:"), diff --git a/mobile/lib/generated/intl/messages_de.dart b/mobile/lib/generated/intl/messages_de.dart index 342921c92d..f133f65b6e 100644 --- a/mobile/lib/generated/intl/messages_de.dart +++ b/mobile/lib/generated/intl/messages_de.dart @@ -1851,6 +1851,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Ƅlteste zuerst"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Abgeschlossen"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Wiederherstellung starten"), "startBackup": @@ -1945,6 +1947,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Dies ist deine Verifizierungs-ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Dadurch wirst du von folgendem GerƤt abgemeldet:"), diff --git a/mobile/lib/generated/intl/messages_el.dart b/mobile/lib/generated/intl/messages_el.dart index 1d2301bc68..a0db9af0ee 100644 --- a/mobile/lib/generated/intl/messages_el.dart +++ b/mobile/lib/generated/intl/messages_el.dart @@ -49,6 +49,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 1640b095ea..053c845954 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -38,6 +38,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m16(albumName) => "Added successfully to ${albumName}"; + static String m95(name) => "Admiring ${name}"; + static String m17(count) => "${Intl.plural(count, zero: 'No Participants', one: '1 Participant', other: '${count} Participants')}"; @@ -46,6 +48,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m19(freeAmount, storageUnit) => "${freeAmount} ${storageUnit} free"; + static String m96(name) => "Scenery with ${name}"; + static String m20(paymentProvider) => "Please cancel your existing subscription from ${paymentProvider} first"; @@ -100,8 +104,12 @@ class MessageLookup extends MessageLookupByLibrary { static String m36(email) => "${email} does not have an Ente account.\n\nSend them an invite to share photos."; + static String m97(name) => "Embracing ${name}"; + static String m37(text) => "Extra photos found for ${text}"; + static String m98(name) => "Feasting with ${name}"; + static String m38(count, formattedNumber) => "${Intl.plural(count, one: '1 file', other: '${formattedNumber} files')} on this device have been backed up safely"; @@ -124,9 +132,13 @@ class MessageLookup extends MessageLookupByLibrary { static String m44(currentlyProcessing, totalCount) => "Processing ${currentlyProcessing} / ${totalCount}"; + static String m99(name) => "Hiking with ${name}"; + static String m45(count) => "${Intl.plural(count, one: '${count} item', other: '${count} items')}"; + static String m100(name) => "Last time with ${name}"; + static String m46(email) => "${email} has invited you to be a trusted contact"; @@ -152,12 +164,18 @@ class MessageLookup extends MessageLookupByLibrary { static String m54(familyAdminEmail) => "Please contact ${familyAdminEmail} to change your code."; + static String m101(name) => "Party with ${name}"; + static String m0(passwordStrengthValue) => "Password strength: ${passwordStrengthValue}"; static String m55(providerName) => "Please talk to ${providerName} support if you were charged"; + static String m102(name, age) => "${name} is ${age}!"; + + static String m103(name, age) => "${name} turning ${age} soon"; + static String m1(count) => "${Intl.plural(count, zero: 'No photos', one: '1 photo', other: '${count} photos')}"; @@ -171,6 +189,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m59(toEmail) => "Please send the logs to \n${toEmail}"; + static String m104(name) => "Posing with ${name}"; + static String m60(folderName) => "Processing ${folderName}..."; static String m61(storeName) => "Rate us on ${storeName}"; @@ -193,6 +213,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m68(endDate) => "Subscription renews on ${endDate}"; + static String m105(name) => "Road trip with ${name}"; + static String m69(count) => "${Intl.plural(count, one: '${count} result found', other: '${count} results found')}"; @@ -204,6 +226,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m71(count, yourCount) => "${count} selected (${yourCount} yours)"; + static String m106(name) => "Selfies with ${name}"; + static String m72(verificationID) => "Here\'s my verification ID: ${verificationID} for ente.io."; @@ -226,6 +250,10 @@ class MessageLookup extends MessageLookupByLibrary { static String m78(fileType) => "This ${fileType} will be deleted from Ente."; + static String m107(name) => "Sports with ${name}"; + + static String m108(name) => "Spotlight on ${name}"; + static String m2(storageAmountInGB) => "${storageAmountInGB} GB"; static String m79( @@ -249,9 +277,18 @@ class MessageLookup extends MessageLookupByLibrary { static String m84(email) => "This is ${email}\'s Verification ID"; + static String m109(count) => + "${Intl.plural(count, one: 'This week, ${count} year ago', other: 'This week, ${count} years ago')}"; + + static String m110(dateFormat) => "${dateFormat} through the years"; + static String m85(count) => "${Intl.plural(count, zero: 'Soon', one: '1 day', other: '${count} days')}"; + static String m111(year) => "Trip in ${year}"; + + static String m112(location) => "Trip to ${location}"; + static String m86(email) => "You have been invited to be a legacy contact by ${email}."; @@ -274,6 +311,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m93(count) => "${Intl.plural(count, one: '${count} year ago', other: '${count} years ago')}"; + static String m113(name) => "You and ${name}"; + static String m94(storageSaved) => "You have successfully freed up ${storageSaved}!"; @@ -333,6 +372,7 @@ class MessageLookup extends MessageLookupByLibrary { "addedSuccessfullyTo": m16, "addingToFavorites": MessageLookupByLibrary.simpleMessage("Adding to favorites..."), + "admiringThem": m95, "advanced": MessageLookupByLibrary.simpleMessage("Advanced"), "advancedSettings": MessageLookupByLibrary.simpleMessage("Advanced"), "after1Day": MessageLookupByLibrary.simpleMessage("After 1 day"), @@ -475,6 +515,7 @@ class MessageLookup extends MessageLookupByLibrary { "availableStorageSpace": m19, "backedUpFolders": MessageLookupByLibrary.simpleMessage("Backed up folders"), + "backgroundWithThem": m96, "backup": MessageLookupByLibrary.simpleMessage("Backup"), "backupFailed": MessageLookupByLibrary.simpleMessage("Backup failed"), "backupFile": MessageLookupByLibrary.simpleMessage("Backup file"), @@ -486,6 +527,7 @@ class MessageLookup extends MessageLookupByLibrary { "backupStatusDescription": MessageLookupByLibrary.simpleMessage( "Items that have been backed up will show up here"), "backupVideos": MessageLookupByLibrary.simpleMessage("Backup videos"), + "beach": MessageLookupByLibrary.simpleMessage("Sand and sea"), "birthday": MessageLookupByLibrary.simpleMessage("Birthday"), "blackFridaySale": MessageLookupByLibrary.simpleMessage("Black Friday Sale"), @@ -556,6 +598,7 @@ class MessageLookup extends MessageLookupByLibrary { "checking": MessageLookupByLibrary.simpleMessage("Checking..."), "checkingModels": MessageLookupByLibrary.simpleMessage("Checking models..."), + "city": MessageLookupByLibrary.simpleMessage("In the city"), "claimFreeStorage": MessageLookupByLibrary.simpleMessage("Claim free storage"), "claimMore": MessageLookupByLibrary.simpleMessage("Claim more!"), @@ -821,6 +864,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Email verification"), "emailYourLogs": MessageLookupByLibrary.simpleMessage("Email your logs"), + "embracingThem": m97, "emergencyContacts": MessageLookupByLibrary.simpleMessage("Emergency Contacts"), "empty": MessageLookupByLibrary.simpleMessage("Empty"), @@ -932,6 +976,7 @@ class MessageLookup extends MessageLookupByLibrary { "faq": MessageLookupByLibrary.simpleMessage("FAQ"), "faqs": MessageLookupByLibrary.simpleMessage("FAQs"), "favorite": MessageLookupByLibrary.simpleMessage("Favorite"), + "feastingWithThem": m98, "feedback": MessageLookupByLibrary.simpleMessage("Feedback"), "file": MessageLookupByLibrary.simpleMessage("File"), "fileFailedToSaveToGallery": MessageLookupByLibrary.simpleMessage( @@ -955,6 +1000,7 @@ class MessageLookup extends MessageLookupByLibrary { "findThemQuickly": MessageLookupByLibrary.simpleMessage("Find them quickly"), "flip": MessageLookupByLibrary.simpleMessage("Flip"), + "food": MessageLookupByLibrary.simpleMessage("Culinary delight"), "forYourMemories": MessageLookupByLibrary.simpleMessage("for your memories"), "forgotPassword": @@ -988,6 +1034,7 @@ class MessageLookup extends MessageLookupByLibrary { "Please allow access to all photos in the Settings app"), "grantPermission": MessageLookupByLibrary.simpleMessage("Grant permission"), + "greenery": MessageLookupByLibrary.simpleMessage("The green life"), "groupNearbyPhotos": MessageLookupByLibrary.simpleMessage("Group nearby photos"), "guestView": MessageLookupByLibrary.simpleMessage("Guest view"), @@ -1008,6 +1055,7 @@ class MessageLookup extends MessageLookupByLibrary { "hideSharedItemsFromHomeGallery": MessageLookupByLibrary.simpleMessage( "Hide shared items from home gallery"), "hiding": MessageLookupByLibrary.simpleMessage("Hiding..."), + "hikingWithThem": m99, "hostedAtOsmFrance": MessageLookupByLibrary.simpleMessage("Hosted at OSM France"), "howItWorks": MessageLookupByLibrary.simpleMessage("How it works"), @@ -1082,7 +1130,10 @@ class MessageLookup extends MessageLookupByLibrary { "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage( "Kindly help us with this information"), "language": MessageLookupByLibrary.simpleMessage("Language"), + "lastTimeWithThem": m100, "lastUpdated": MessageLookupByLibrary.simpleMessage("Last updated"), + "lastYearsTrip": + MessageLookupByLibrary.simpleMessage("Last year\'s trip"), "leave": MessageLookupByLibrary.simpleMessage("Leave"), "leaveAlbum": MessageLookupByLibrary.simpleMessage("Leave album"), "leaveFamily": MessageLookupByLibrary.simpleMessage("Leave family"), @@ -1228,9 +1279,11 @@ class MessageLookup extends MessageLookupByLibrary { "moments": MessageLookupByLibrary.simpleMessage("Moments"), "month": MessageLookupByLibrary.simpleMessage("month"), "monthly": MessageLookupByLibrary.simpleMessage("Monthly"), + "moon": MessageLookupByLibrary.simpleMessage("In the moonlight"), "moreDetails": MessageLookupByLibrary.simpleMessage("More details"), "mostRecent": MessageLookupByLibrary.simpleMessage("Most recent"), "mostRelevant": MessageLookupByLibrary.simpleMessage("Most relevant"), + "mountains": MessageLookupByLibrary.simpleMessage("Over the hills"), "moveItem": m50, "moveSelectedPhotosToOneDate": MessageLookupByLibrary.simpleMessage( "Move selected photos to one date"), @@ -1303,6 +1356,7 @@ class MessageLookup extends MessageLookupByLibrary { "onDevice": MessageLookupByLibrary.simpleMessage("On device"), "onEnte": MessageLookupByLibrary.simpleMessage( "On ente"), + "onTheRoad": MessageLookupByLibrary.simpleMessage("On the road again"), "onlyFamilyAdminCanChangeCode": m54, "onlyThem": MessageLookupByLibrary.simpleMessage("Only them"), "oops": MessageLookupByLibrary.simpleMessage("Oops"), @@ -1332,6 +1386,7 @@ class MessageLookup extends MessageLookupByLibrary { "pairingComplete": MessageLookupByLibrary.simpleMessage("Pairing complete"), "panorama": MessageLookupByLibrary.simpleMessage("Panorama"), + "partyWithThem": m101, "passKeyPendingVerification": MessageLookupByLibrary.simpleMessage( "Verification is still pending"), "passkey": MessageLookupByLibrary.simpleMessage("Passkey"), @@ -1363,7 +1418,10 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Permanently delete"), "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( "Permanently delete from device?"), + "personIsAge": m102, "personName": MessageLookupByLibrary.simpleMessage("Person name"), + "personTurningAge": m103, + "pets": MessageLookupByLibrary.simpleMessage("Furry companions"), "photoDescriptions": MessageLookupByLibrary.simpleMessage("Photo descriptions"), "photoGridSize": @@ -1418,6 +1476,7 @@ class MessageLookup extends MessageLookupByLibrary { "Please wait for sometime before retrying"), "pleaseWaitThisWillTakeAWhile": MessageLookupByLibrary.simpleMessage( "Please wait, this will take a while."), + "posingWithThem": m104, "preparingLogs": MessageLookupByLibrary.simpleMessage("Preparing logs..."), "preserveMore": MessageLookupByLibrary.simpleMessage("Preserve more"), @@ -1570,6 +1629,7 @@ class MessageLookup extends MessageLookupByLibrary { "reviewSuggestions": MessageLookupByLibrary.simpleMessage("Review suggestions"), "right": MessageLookupByLibrary.simpleMessage("Right"), + "roadtripWithThem": m105, "rotate": MessageLookupByLibrary.simpleMessage("Rotate"), "rotateLeft": MessageLookupByLibrary.simpleMessage("Rotate left"), "rotateRight": MessageLookupByLibrary.simpleMessage("Rotate right"), @@ -1675,6 +1735,7 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "selectedPhotos": m7, "selectedPhotosWithYours": m71, + "selfiesWithThem": m106, "send": MessageLookupByLibrary.simpleMessage("Send"), "sendEmail": MessageLookupByLibrary.simpleMessage("Send email"), "sendInvite": MessageLookupByLibrary.simpleMessage("Send invite"), @@ -1778,6 +1839,10 @@ class MessageLookup extends MessageLookupByLibrary { "sortNewestFirst": MessageLookupByLibrary.simpleMessage("Newest first"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Oldest first"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Success"), + "sportsWithThem": m107, + "spotlightOnThem": m108, + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Start recovery"), "startBackup": MessageLookupByLibrary.simpleMessage("Start backup"), @@ -1812,6 +1877,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Successfully unhid"), "suggestFeatures": MessageLookupByLibrary.simpleMessage("Suggest features"), + "sunrise": MessageLookupByLibrary.simpleMessage("On the horizon"), "support": MessageLookupByLibrary.simpleMessage("Support"), "syncProgress": m82, "syncStopped": MessageLookupByLibrary.simpleMessage("Sync stopped"), @@ -1866,6 +1932,9 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "This is your Verification ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), + "thisWeekXYearsAgo": m109, "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "This will log you out of the following device:"), @@ -1877,6 +1946,7 @@ class MessageLookup extends MessageLookupByLibrary { "thisWillRemovePublicLinksOfAllSelectedQuickLinks": MessageLookupByLibrary.simpleMessage( "This will remove public links of all selected quick links."), + "throughTheYears": m110, "toEnableAppLockPleaseSetupDevicePasscodeOrScreen": MessageLookupByLibrary.simpleMessage( "To enable app lock, please setup device passcode or screen lock in your system settings."), @@ -1892,6 +1962,8 @@ class MessageLookup extends MessageLookupByLibrary { "trash": MessageLookupByLibrary.simpleMessage("Trash"), "trashDaysLeft": m85, "trim": MessageLookupByLibrary.simpleMessage("Trim"), + "tripInYear": m111, + "tripToLocation": m112, "trustedContacts": MessageLookupByLibrary.simpleMessage("Trusted contacts"), "trustedInviteBody": m86, @@ -2025,6 +2097,7 @@ class MessageLookup extends MessageLookupByLibrary { "yesResetPerson": MessageLookupByLibrary.simpleMessage("Yes, reset person"), "you": MessageLookupByLibrary.simpleMessage("You"), + "youAndThem": m113, "youAreOnAFamilyPlan": MessageLookupByLibrary.simpleMessage("You are on a family plan!"), "youAreOnTheLatestVersion": MessageLookupByLibrary.simpleMessage( diff --git a/mobile/lib/generated/intl/messages_es.dart b/mobile/lib/generated/intl/messages_es.dart index c4d2a8c60a..b04da60fbb 100644 --- a/mobile/lib/generated/intl/messages_es.dart +++ b/mobile/lib/generated/intl/messages_es.dart @@ -1868,6 +1868,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("MĆ”s antiguos primero"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Ɖxito"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Iniciar la recuperación"), "startBackup": @@ -1961,6 +1963,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Esta es tu ID de verificación"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Esto cerrarĆ” la sesión del siguiente dispositivo:"), diff --git a/mobile/lib/generated/intl/messages_et.dart b/mobile/lib/generated/intl/messages_et.dart index 4cf41f1736..a36752fdd9 100644 --- a/mobile/lib/generated/intl/messages_et.dart +++ b/mobile/lib/generated/intl/messages_et.dart @@ -254,6 +254,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Uuemad eespool"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Vanemad eespool"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "storage": MessageLookupByLibrary.simpleMessage("MƤluruum"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("Perekond"), @@ -270,6 +272,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Tingimused"), "theme": MessageLookupByLibrary.simpleMessage("Teema"), "thisDevice": MessageLookupByLibrary.simpleMessage("See seade"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_fa.dart b/mobile/lib/generated/intl/messages_fa.dart index 7b9333fcbd..fb7d670540 100644 --- a/mobile/lib/generated/intl/messages_fa.dart +++ b/mobile/lib/generated/intl/messages_fa.dart @@ -389,6 +389,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ایتدا Ų¬ŲÆŪŒŲÆŲŖŲ±ŪŒŁ†"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("ایتدا Ł‚ŲÆŪŒŁ…ŪŒā€ŒŲŖŲ±ŪŒŁ†"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startBackup": MessageLookupByLibrary.simpleMessage("ؓروع Ł¾Ų“ŲŖŪŒŲØŲ§Ł† گیری"), "status": MessageLookupByLibrary.simpleMessage("وضعیت"), @@ -414,6 +416,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ŲÆŲ§Ł†Ł„ŁˆŲÆ کامل نؓد"), "theme": MessageLookupByLibrary.simpleMessage("ŲŖŁ…"), "thisDevice": MessageLookupByLibrary.simpleMessage("Ų§ŪŒŁ† دستگاه"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "ŲØŲ§ Ų§ŪŒŁ† کار Ų“Ł…Ų§ Ų§Ų² دستگاه زیر Ų®Ų§Ų±Ų¬ Ł…ŪŒā€ŒŲ“ŁˆŪŒŲÆ:"), diff --git a/mobile/lib/generated/intl/messages_fr.dart b/mobile/lib/generated/intl/messages_fr.dart index b9b1aeae34..575439474d 100644 --- a/mobile/lib/generated/intl/messages_fr.dart +++ b/mobile/lib/generated/intl/messages_fr.dart @@ -1893,6 +1893,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Plus ancien en premier"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ SuccĆØs"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("DĆ©marrer la rĆ©cupĆ©ration"), "startBackup": @@ -1988,6 +1990,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Ceci est votre ID de vĆ©rification"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Cela vous dĆ©connectera de l\'appareil suivant :"), diff --git a/mobile/lib/generated/intl/messages_gu.dart b/mobile/lib/generated/intl/messages_gu.dart index 16e8999d69..21b9a034a4 100644 --- a/mobile/lib/generated/intl/messages_gu.dart +++ b/mobile/lib/generated/intl/messages_gu.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_he.dart b/mobile/lib/generated/intl/messages_he.dart index fed778501e..a76c05098e 100644 --- a/mobile/lib/generated/intl/messages_he.dart +++ b/mobile/lib/generated/intl/messages_he.dart @@ -848,6 +848,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("הישן ביותר קודם"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ הצלחה"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startBackup": MessageLookupByLibrary.simpleMessage("×”×Ŗ×—×œ גיבוי"), "storage": MessageLookupByLibrary.simpleMessage("אחהון"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("משפחה"), @@ -887,6 +889,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("זה מזהה ×”××™×ž×•×Ŗ שלך"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage("זה ×™× ×Ŗ×§ ××•×Ŗ×š ×ž×”×ž×›×©×™×Ø הבא:"), "thisWillLogYouOutOfThisDevice": diff --git a/mobile/lib/generated/intl/messages_hi.dart b/mobile/lib/generated/intl/messages_hi.dart index dd54fb6dbe..0a5b0dcd6a 100644 --- a/mobile/lib/generated/intl/messages_hi.dart +++ b/mobile/lib/generated/intl/messages_hi.dart @@ -119,10 +119,14 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "ą¤•ą„ą¤› गऔ़बऔ़ ą¤¹ą„ą¤ˆ ą¤¹ą„ˆą„¤ ą¤•ą„ƒą¤Ŗą¤Æą¤¾ ą¤¦ą„‹ą¤¬ą¤¾ą¤°ą¤¾ ą¤Ŗą„ą¤°ą¤Æą¤¾ą¤ø ą¤•ą¤°ą„‡ą¤‚ą„¤"), "sorry": MessageLookupByLibrary.simpleMessage("ą¤•ą„ą¤·ą¤®ą¤¾ ą¤•ą¤°ą„‡ą¤‚!"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "terminate": MessageLookupByLibrary.simpleMessage("ą¤°ą¤¦ą„ą¤¦ ą¤•ą¤°ą„‡ą¤‚"), "terminateSession": MessageLookupByLibrary.simpleMessage("ą¤øą„‡ą¤¶ą¤Ø ą¤°ą¤¦ą„ą¤¦ ą¤•ą¤°ą„‡ą¤‚?"), "thisDevice": MessageLookupByLibrary.simpleMessage("यह औिवाइस"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "ą¤‡ą¤øą¤øą„‡ आप इन ą¤”ą¤æą¤µą¤¾ą¤‡ą¤øą„‹ą¤‚ ą¤øą„‡ ą¤²ą„‰ą¤— ą¤†ą¤‰ą¤Ÿ ą¤¹ą„‹ ą¤œą¤¾ą¤ą¤ą¤—ą„‡:"), diff --git a/mobile/lib/generated/intl/messages_hu.dart b/mobile/lib/generated/intl/messages_hu.dart index 06f95ad74f..72a47f10d1 100644 --- a/mobile/lib/generated/intl/messages_hu.dart +++ b/mobile/lib/generated/intl/messages_hu.dart @@ -66,6 +66,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_id.dart b/mobile/lib/generated/intl/messages_id.dart index 746fab8327..ed8740da74 100644 --- a/mobile/lib/generated/intl/messages_id.dart +++ b/mobile/lib/generated/intl/messages_id.dart @@ -1326,6 +1326,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortNewestFirst": MessageLookupByLibrary.simpleMessage("Terbaru dulu"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Terlama dulu"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Berhasil"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startBackup": MessageLookupByLibrary.simpleMessage("Mulai pencadangan"), "status": MessageLookupByLibrary.simpleMessage("Status"), @@ -1400,6 +1402,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Ini adalah ID Verifikasi kamu"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Ini akan mengeluarkan akunmu dari perangkat berikut:"), diff --git a/mobile/lib/generated/intl/messages_it.dart b/mobile/lib/generated/intl/messages_it.dart index 31ff24ed1a..543ebf787a 100644 --- a/mobile/lib/generated/intl/messages_it.dart +++ b/mobile/lib/generated/intl/messages_it.dart @@ -1791,6 +1791,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Prima le più vecchie"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Operazione riuscita"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Avvia il recupero"), "startBackup": MessageLookupByLibrary.simpleMessage("Avvia backup"), @@ -1883,6 +1885,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Questo ĆØ il tuo ID di verifica"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Verrai disconnesso dai seguenti dispositivi:"), diff --git a/mobile/lib/generated/intl/messages_ja.dart b/mobile/lib/generated/intl/messages_ja.dart index 4bac9a9fa7..367cc1100c 100644 --- a/mobile/lib/generated/intl/messages_ja.dart +++ b/mobile/lib/generated/intl/messages_ja.dart @@ -1585,6 +1585,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortNewestFirst": MessageLookupByLibrary.simpleMessage("ꖰ恗恄順"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("å¤ć„é †"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("成功✨"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("ćƒŖć‚«ćƒćƒŖć‚’é–‹å§‹"), "startBackup": MessageLookupByLibrary.simpleMessage("ćƒćƒƒć‚Æć‚¢ćƒƒćƒ—ć‚’é–‹å§‹"), @@ -1662,6 +1664,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("ć“ć‚ŒćÆć‚ćŖćŸć®čŖčØ¼ID恧恙"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage("ä»„äø‹ć®ćƒ‡ćƒć‚¤ć‚¹ć‹ć‚‰ćƒ­ć‚°ć‚¢ć‚¦ćƒˆć—ć¾ć™:"), "thisWillLogYouOutOfThisDevice": diff --git a/mobile/lib/generated/intl/messages_km.dart b/mobile/lib/generated/intl/messages_km.dart index 985bc1fd81..0a6ce004f9 100644 --- a/mobile/lib/generated/intl/messages_km.dart +++ b/mobile/lib/generated/intl/messages_km.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_ko.dart b/mobile/lib/generated/intl/messages_ko.dart index d710580623..be8625e623 100644 --- a/mobile/lib/generated/intl/messages_ko.dart +++ b/mobile/lib/generated/intl/messages_ko.dart @@ -68,6 +68,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_lt.dart b/mobile/lib/generated/intl/messages_lt.dart index ebfe671bef..2ae2983178 100644 --- a/mobile/lib/generated/intl/messages_lt.dart +++ b/mobile/lib/generated/intl/messages_lt.dart @@ -1578,6 +1578,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Seniausią pirmiausiai"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Sėkmė"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Pradėti atkÅ«rimą"), "startBackup": MessageLookupByLibrary.simpleMessage( @@ -1652,6 +1654,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Tai – jÅ«sų patvirtinimo ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Tai jus atjungs nuo toliau nurodyto ÄÆrenginio:"), diff --git a/mobile/lib/generated/intl/messages_ml.dart b/mobile/lib/generated/intl/messages_ml.dart index ec7aa280e4..ff781c6868 100644 --- a/mobile/lib/generated/intl/messages_ml.dart +++ b/mobile/lib/generated/intl/messages_ml.dart @@ -149,6 +149,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortAlbumsBy": MessageLookupByLibrary.simpleMessage("ą“‡ą“Ŗąµą“°ą“•ą“¾ą“°ą“‚ ą“…ą“Ÿąµą“•ąµą“•ąµą“•"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ ą“øą“«ą“²ą“‚"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "strongStrength": MessageLookupByLibrary.simpleMessage("ą“¶ą“•ąµą“¤ą“‚"), "success": MessageLookupByLibrary.simpleMessage("ą“øą“«ą“²ą“‚"), "support": MessageLookupByLibrary.simpleMessage("ą“Ŗą“æą“Øąµą“¤ąµą“£"), @@ -157,6 +159,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("ą“Øą“æą“¬ą“Øąµą“§ą“Øą“•ąµ¾"), "thankYou": MessageLookupByLibrary.simpleMessage("ą“Øą“Øąµą“¦ą“æ"), "thisDevice": MessageLookupByLibrary.simpleMessage("ą“ˆ ą“‰ą“Ŗą“•ą“°ą“£ą“‚"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_nl.dart b/mobile/lib/generated/intl/messages_nl.dart index e0a5d2ce04..5bd3e8c1ea 100644 --- a/mobile/lib/generated/intl/messages_nl.dart +++ b/mobile/lib/generated/intl/messages_nl.dart @@ -1848,6 +1848,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Nieuwste eerst"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Oudste eerst"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Succes"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Herstel starten"), "startBackup": MessageLookupByLibrary.simpleMessage("Back-up starten"), @@ -1940,6 +1942,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Dit is uw verificatie-ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Dit zal je uitloggen van het volgende apparaat:"), diff --git a/mobile/lib/generated/intl/messages_no.dart b/mobile/lib/generated/intl/messages_no.dart index c6b217ea08..fa13faccb0 100644 --- a/mobile/lib/generated/intl/messages_no.dart +++ b/mobile/lib/generated/intl/messages_no.dart @@ -419,6 +419,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "Beklager, vi kunne ikke generere sikre nĆøkler pĆ„ denne enheten.\n\nvennligst registrer deg fra en annen enhet."), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Suksess"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "status": MessageLookupByLibrary.simpleMessage("Status"), "strongStrength": MessageLookupByLibrary.simpleMessage("Sterkt"), "tapToCopy": @@ -436,6 +438,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Dette er din bekreftelses-ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Dette vil logge deg ut av fĆølgende enhet:"), diff --git a/mobile/lib/generated/intl/messages_pl.dart b/mobile/lib/generated/intl/messages_pl.dart index 36a477f014..4d32415fd5 100644 --- a/mobile/lib/generated/intl/messages_pl.dart +++ b/mobile/lib/generated/intl/messages_pl.dart @@ -1793,6 +1793,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Od najstarszych"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Sukces"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Rozpocznij odzyskiwanie"), "startBackup": MessageLookupByLibrary.simpleMessage( @@ -1884,6 +1886,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "To jest Twój Identyfikator Weryfikacji"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "To wyloguje Cię z tego urządzenia:"), diff --git a/mobile/lib/generated/intl/messages_pt.dart b/mobile/lib/generated/intl/messages_pt.dart index 3d4cf0844d..3a250ecb55 100644 --- a/mobile/lib/generated/intl/messages_pt.dart +++ b/mobile/lib/generated/intl/messages_pt.dart @@ -1836,6 +1836,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Antigos primeiro"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Sucesso"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Iniciar recuperação"), "startBackup": @@ -1928,6 +1930,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Este Ć© o seu ID de verificação"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Isso farĆ” vocĆŖ sair do dispositivo a seguir:"), diff --git a/mobile/lib/generated/intl/messages_ro.dart b/mobile/lib/generated/intl/messages_ro.dart index 2a0b9f2167..b9fc7139bf 100644 --- a/mobile/lib/generated/intl/messages_ro.dart +++ b/mobile/lib/generated/intl/messages_ro.dart @@ -1809,6 +1809,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Cele mai vechi primele"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Succes"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Ǝncepeți recuperarea"), "startBackup": @@ -1899,6 +1901,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Acesta este ID-ul dvs. de verificare"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Urmează să vă deconectați de pe următorul dispozitiv:"), diff --git a/mobile/lib/generated/intl/messages_ru.dart b/mobile/lib/generated/intl/messages_ru.dart index a2fe3b2c7d..a2b6149094 100644 --- a/mobile/lib/generated/intl/messages_ru.dart +++ b/mobile/lib/generated/intl/messages_ru.dart @@ -1700,6 +1700,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Дначала старые"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Успешно"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startBackup": MessageLookupByLibrary.simpleMessage( "ŠŠ°Ń‡Š°Ń‚ŃŒ резервное копирование"), "status": MessageLookupByLibrary.simpleMessage("Š”Ń‚Š°Ń‚ŃƒŃ"), @@ -1784,6 +1786,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Это ваш иГентификатор ŠæŠ¾Š“Ń‚Š²ŠµŃ€Š¶Š“ŠµŠ½ŠøŃ"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Š’Ń‹ выйГете ŠøŠ· списка ŃŠ»ŠµŠ“ŃƒŃŽŃ‰ŠøŃ… ŃƒŃŃ‚Ń€Š¾Š¹ŃŃ‚Š²:"), diff --git a/mobile/lib/generated/intl/messages_sl.dart b/mobile/lib/generated/intl/messages_sl.dart index 447a6c38e3..ec46090eaa 100644 --- a/mobile/lib/generated/intl/messages_sl.dart +++ b/mobile/lib/generated/intl/messages_sl.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_sv.dart b/mobile/lib/generated/intl/messages_sv.dart index 856bd342fa..08f11cbc18 100644 --- a/mobile/lib/generated/intl/messages_sv.dart +++ b/mobile/lib/generated/intl/messages_sv.dart @@ -638,6 +638,8 @@ class MessageLookup extends MessageLookupByLibrary { "TyvƤrr, vi kunde inte generera sƤkra nycklar pĆ„ den hƤr enheten.\n\nVƤnligen registrera dig frĆ„n en annan enhet."), "sort": MessageLookupByLibrary.simpleMessage("Sortera"), "sortAlbumsBy": MessageLookupByLibrary.simpleMessage("Sortera efter"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "status": MessageLookupByLibrary.simpleMessage("Status"), "storageBreakupYou": MessageLookupByLibrary.simpleMessage("Du"), "storageInGB": m2, @@ -668,6 +670,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Detta Ƥr ditt verifierings-ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Detta kommer att logga ut dig frĆ„n fƶljande enhet:"), diff --git a/mobile/lib/generated/intl/messages_ta.dart b/mobile/lib/generated/intl/messages_ta.dart index 5d75cde490..0b2e3e577c 100644 --- a/mobile/lib/generated/intl/messages_ta.dart +++ b/mobile/lib/generated/intl/messages_ta.dart @@ -76,6 +76,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_te.dart b/mobile/lib/generated/intl/messages_te.dart index b282c236d1..3eca5887bb 100644 --- a/mobile/lib/generated/intl/messages_te.dart +++ b/mobile/lib/generated/intl/messages_te.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_th.dart b/mobile/lib/generated/intl/messages_th.dart index ffadc1adde..94400b311a 100644 --- a/mobile/lib/generated/intl/messages_th.dart +++ b/mobile/lib/generated/intl/messages_th.dart @@ -330,6 +330,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "ąø”ąøµąøšąø²ąø‡ąø­ąø¢ą¹ˆąø²ąø‡ąøœąø“ąø”ąøžąø„ąø²ąø” โปรดคองอีกครั้ง"), "sorry": MessageLookupByLibrary.simpleMessage("ขออภัย"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "status": MessageLookupByLibrary.simpleMessage("สถานะ"), "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("ąø„ąø£ąø­ąøšąø„ąø£ąø±ąø§"), @@ -348,6 +350,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage( "ąø„ąøµąø¢ą¹Œąøąø²ąø£ąøąø¹ą¹‰ąø„ąø·ąø™ąø—ąøµą¹ˆąø„ąøøąø“ąø›ą¹‰ąø­ąø™ą¹„ąø”ą¹ˆąø–ąø¹ąøąø•ą¹‰ąø­ąø‡"), "thisDevice": MessageLookupByLibrary.simpleMessage("ąø­ąøøąø›ąøąø£ąø“ą¹Œąø™ąøµą¹‰"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same."), diff --git a/mobile/lib/generated/intl/messages_ti.dart b/mobile/lib/generated/intl/messages_ti.dart index f889f36ec0..2b5dd741f9 100644 --- a/mobile/lib/generated/intl/messages_ti.dart +++ b/mobile/lib/generated/intl/messages_ti.dart @@ -50,6 +50,10 @@ class MessageLookup extends MessageLookupByLibrary { "Selected items will be removed from this person, but not deleted from your library."), "shiftDatesAndTime": MessageLookupByLibrary.simpleMessage("Shift dates and time"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillMakeTheDateAndTimeOfAllSelected": MessageLookupByLibrary.simpleMessage( "This will make the date and time of all selected photos the same.") diff --git a/mobile/lib/generated/intl/messages_tr.dart b/mobile/lib/generated/intl/messages_tr.dart index 3886130565..2cf9cbc061 100644 --- a/mobile/lib/generated/intl/messages_tr.dart +++ b/mobile/lib/generated/intl/messages_tr.dart @@ -1829,6 +1829,8 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Yeniden eskiye"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Ɩnce en eski"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Başarılı"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("Kurtarmayı başlat"), "startBackup": @@ -1922,6 +1924,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Doğrulama kimliğiniz"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Bu, sizi aşağıdaki cihazdan Ƨıkış yapacak:"), diff --git a/mobile/lib/generated/intl/messages_uk.dart b/mobile/lib/generated/intl/messages_uk.dart index 33d5f9c7d3..100a5a7593 100644 --- a/mobile/lib/generated/intl/messages_uk.dart +++ b/mobile/lib/generated/intl/messages_uk.dart @@ -1782,6 +1782,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("Š”ŠæŠ¾Ń‡Š°Ń‚ŠŗŃƒ Š½Š°Š¹ŃŃ‚Š°Ń€Ń–ŃˆŃ–"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ Š£ŃŠæŃ–ŃˆŠ½Š¾"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("ŠŸŠ¾Ń‡Š°Ń‚Šø Š²Ń–Š“Š½Š¾Š²Š»ŠµŠ½Š½Ń"), "startBackup": @@ -1872,6 +1874,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( "Це ваш ІГентифікатор ŠæŃ–Š“Ń‚Š²ŠµŃ€Š“Š¶ŠµŠ½Š½Ń"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Це призвеГе Го Š²ŠøŃ…Š¾Š“Ńƒ на Š½Š°ŃŃ‚ŃƒŠæŠ½Š¾Š¼Ńƒ пристрої:"), diff --git a/mobile/lib/generated/intl/messages_vi.dart b/mobile/lib/generated/intl/messages_vi.dart index 5601b4139e..c672284ae5 100644 --- a/mobile/lib/generated/intl/messages_vi.dart +++ b/mobile/lib/generated/intl/messages_vi.dart @@ -1756,6 +1756,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortOldestFirst": MessageLookupByLibrary.simpleMessage("CÅ© nhįŗ„t trước"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ ThĆ nh cĆ“ng"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("BįŗÆt đầu khĆ“i phỄc"), "startBackup": MessageLookupByLibrary.simpleMessage("BįŗÆt đầu sao lʰu"), @@ -1843,6 +1845,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("Đây lĆ  ID xĆ”c minh cį»§a bįŗ”n"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Điều nĆ y sįŗ½ đăng xuįŗ„t bįŗ”n khį»i thiįŗæt bị sau:"), diff --git a/mobile/lib/generated/intl/messages_zh.dart b/mobile/lib/generated/intl/messages_zh.dart index eefcd9f3dc..08b8ec8507 100644 --- a/mobile/lib/generated/intl/messages_zh.dart +++ b/mobile/lib/generated/intl/messages_zh.dart @@ -1494,6 +1494,8 @@ class MessageLookup extends MessageLookupByLibrary { "sortNewestFirst": MessageLookupByLibrary.simpleMessage("ęœ€ę–°åœØå‰"), "sortOldestFirst": MessageLookupByLibrary.simpleMessage("ęœ€ę—§åœØå‰"), "sparkleSuccess": MessageLookupByLibrary.simpleMessage("✨ 成功"), + "spotlightOnYourself": + MessageLookupByLibrary.simpleMessage("Spotlight on yourself"), "startAccountRecoveryTitle": MessageLookupByLibrary.simpleMessage("å¼€å§‹ę¢å¤"), "startBackup": MessageLookupByLibrary.simpleMessage("开始备份"), @@ -1568,6 +1570,8 @@ class MessageLookup extends MessageLookupByLibrary { "thisIsPersonVerificationId": m84, "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage("čæ™ę˜Æę‚Øēš„éŖŒčÆ ID"), + "thisWeekThroughTheYears": + MessageLookupByLibrary.simpleMessage("This week through the years"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage("čæ™å°†ä½æę‚ØåœØä»„äø‹č®¾å¤‡äø­é€€å‡ŗē™»å½•ļ¼š"), "thisWillLogYouOutOfThisDevice": diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index da9a2b4676..d5a4d214ac 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -11413,6 +11413,318 @@ class S { args: [], ); } + + /// `{dateFormat} through the years` + String throughTheYears(Object dateFormat) { + return Intl.message( + '$dateFormat through the years', + name: 'throughTheYears', + desc: '', + args: [dateFormat], + ); + } + + /// `This week through the years` + String get thisWeekThroughTheYears { + return Intl.message( + 'This week through the years', + name: 'thisWeekThroughTheYears', + desc: '', + args: [], + ); + } + + /// `{count, plural, =1 {This week, {count} year ago} other {This week, {count} years ago}}` + String thisWeekXYearsAgo(num count) { + return Intl.plural( + count, + one: 'This week, $count year ago', + other: 'This week, $count years ago', + name: 'thisWeekXYearsAgo', + desc: '', + args: [count], + ); + } + + /// `You and {name}` + String youAndThem(Object name) { + return Intl.message( + 'You and $name', + name: 'youAndThem', + desc: '', + args: [name], + ); + } + + /// `Admiring {name}` + String admiringThem(Object name) { + return Intl.message( + 'Admiring $name', + name: 'admiringThem', + desc: '', + args: [name], + ); + } + + /// `Embracing {name}` + String embracingThem(Object name) { + return Intl.message( + 'Embracing $name', + name: 'embracingThem', + desc: '', + args: [name], + ); + } + + /// `Party with {name}` + String partyWithThem(Object name) { + return Intl.message( + 'Party with $name', + name: 'partyWithThem', + desc: '', + args: [name], + ); + } + + /// `Hiking with {name}` + String hikingWithThem(Object name) { + return Intl.message( + 'Hiking with $name', + name: 'hikingWithThem', + desc: '', + args: [name], + ); + } + + /// `Feasting with {name}` + String feastingWithThem(Object name) { + return Intl.message( + 'Feasting with $name', + name: 'feastingWithThem', + desc: '', + args: [name], + ); + } + + /// `Selfies with {name}` + String selfiesWithThem(Object name) { + return Intl.message( + 'Selfies with $name', + name: 'selfiesWithThem', + desc: '', + args: [name], + ); + } + + /// `Posing with {name}` + String posingWithThem(Object name) { + return Intl.message( + 'Posing with $name', + name: 'posingWithThem', + desc: '', + args: [name], + ); + } + + /// `Scenery with {name}` + String backgroundWithThem(Object name) { + return Intl.message( + 'Scenery with $name', + name: 'backgroundWithThem', + desc: '', + args: [name], + ); + } + + /// `Sports with {name}` + String sportsWithThem(Object name) { + return Intl.message( + 'Sports with $name', + name: 'sportsWithThem', + desc: '', + args: [name], + ); + } + + /// `Road trip with {name}` + String roadtripWithThem(Object name) { + return Intl.message( + 'Road trip with $name', + name: 'roadtripWithThem', + desc: '', + args: [name], + ); + } + + /// `Spotlight on yourself` + String get spotlightOnYourself { + return Intl.message( + 'Spotlight on yourself', + name: 'spotlightOnYourself', + desc: '', + args: [], + ); + } + + /// `Spotlight on {name}` + String spotlightOnThem(Object name) { + return Intl.message( + 'Spotlight on $name', + name: 'spotlightOnThem', + desc: '', + args: [name], + ); + } + + /// `{name} is {age}!` + String personIsAge(Object name, Object age) { + return Intl.message( + '$name is $age!', + name: 'personIsAge', + desc: '', + args: [name, age], + ); + } + + /// `{name} turning {age} soon` + String personTurningAge(Object name, Object age) { + return Intl.message( + '$name turning $age soon', + name: 'personTurningAge', + desc: '', + args: [name, age], + ); + } + + /// `Last time with {name}` + String lastTimeWithThem(Object name) { + return Intl.message( + 'Last time with $name', + name: 'lastTimeWithThem', + desc: '', + args: [name], + ); + } + + /// `Trip to {location}` + String tripToLocation(Object location) { + return Intl.message( + 'Trip to $location', + name: 'tripToLocation', + desc: '', + args: [location], + ); + } + + /// `Trip in {year}` + String tripInYear(Object year) { + return Intl.message( + 'Trip in $year', + name: 'tripInYear', + desc: '', + args: [year], + ); + } + + /// `Last year's trip` + String get lastYearsTrip { + return Intl.message( + 'Last year\'s trip', + name: 'lastYearsTrip', + desc: '', + args: [], + ); + } + + /// `On the horizon` + String get sunrise { + return Intl.message( + 'On the horizon', + name: 'sunrise', + desc: '', + args: [], + ); + } + + /// `Over the hills` + String get mountains { + return Intl.message( + 'Over the hills', + name: 'mountains', + desc: '', + args: [], + ); + } + + /// `The green life` + String get greenery { + return Intl.message( + 'The green life', + name: 'greenery', + desc: '', + args: [], + ); + } + + /// `Sand and sea` + String get beach { + return Intl.message( + 'Sand and sea', + name: 'beach', + desc: '', + args: [], + ); + } + + /// `In the city` + String get city { + return Intl.message( + 'In the city', + name: 'city', + desc: '', + args: [], + ); + } + + /// `In the moonlight` + String get moon { + return Intl.message( + 'In the moonlight', + name: 'moon', + desc: '', + args: [], + ); + } + + /// `On the road again` + String get onTheRoad { + return Intl.message( + 'On the road again', + name: 'onTheRoad', + desc: '', + args: [], + ); + } + + /// `Culinary delight` + String get food { + return Intl.message( + 'Culinary delight', + name: 'food', + desc: '', + args: [], + ); + } + + /// `Furry companions` + String get pets { + return Intl.message( + 'Furry companions', + name: 'pets', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_ar.arb b/mobile/lib/l10n/intl_ar.arb index c42158fb9c..0c9770a16a 100644 --- a/mobile/lib/l10n/intl_ar.arb +++ b/mobile/lib/l10n/intl_ar.arb @@ -38,5 +38,7 @@ "shiftDatesAndTime": "Shift dates and time", "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_be.arb b/mobile/lib/l10n/intl_be.arb index 8b72ff9184..609239746d 100644 --- a/mobile/lib/l10n/intl_be.arb +++ b/mobile/lib/l10n/intl_be.arb @@ -215,5 +215,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_bg.arb b/mobile/lib/l10n/intl_bg.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_bg.arb +++ b/mobile/lib/l10n/intl_bg.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ca.arb b/mobile/lib/l10n/intl_ca.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_ca.arb +++ b/mobile/lib/l10n/intl_ca.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_cs.arb b/mobile/lib/l10n/intl_cs.arb index 9c8222ea1d..67deea0b41 100644 --- a/mobile/lib/l10n/intl_cs.arb +++ b/mobile/lib/l10n/intl_cs.arb @@ -18,5 +18,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_da.arb b/mobile/lib/l10n/intl_da.arb index ae644018b6..ac47e125a7 100644 --- a/mobile/lib/l10n/intl_da.arb +++ b/mobile/lib/l10n/intl_da.arb @@ -258,5 +258,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/lib/l10n/intl_de.arb index 77ae8c4b95..bf52d682dd 100644 --- a/mobile/lib/l10n/intl_de.arb +++ b/mobile/lib/l10n/intl_de.arb @@ -1688,5 +1688,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_el.arb b/mobile/lib/l10n/intl_el.arb index e0c97ff543..34a315866c 100644 --- a/mobile/lib/l10n/intl_el.arb +++ b/mobile/lib/l10n/intl_el.arb @@ -15,5 +15,7 @@ "shiftDatesAndTime": "Shift dates and time", "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index bb3371f2d0..de7d90f924 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1700,5 +1700,36 @@ }, "appIcon": "App icon", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." -} \ No newline at end of file + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "throughTheYears": "{dateFormat} through the years", + "thisWeekThroughTheYears": "This week through the years", + "thisWeekXYearsAgo": "{count, plural, =1 {This week, {count} year ago} other {This week, {count} years ago}}", + "youAndThem": "You and {name}", + "admiringThem": "Admiring {name}", + "embracingThem": "Embracing {name}", + "partyWithThem": "Party with {name}", + "hikingWithThem": "Hiking with {name}", + "feastingWithThem": "Feasting with {name}", + "selfiesWithThem": "Selfies with {name}", + "posingWithThem": "Posing with {name}", + "backgroundWithThem": "Scenery with {name}", + "sportsWithThem": "Sports with {name}", + "roadtripWithThem": "Road trip with {name}", + "spotlightOnYourself": "Spotlight on yourself", + "spotlightOnThem": "Spotlight on {name}", + "personIsAge": "{name} is {age}!", + "personTurningAge": "{name} turning {age} soon", + "lastTimeWithThem": "Last time with {name}", + "tripToLocation": "Trip to {location}", + "tripInYear": "Trip in {year}", + "lastYearsTrip": "Last year's trip", + "sunrise": "On the horizon", + "mountains": "Over the hills", + "greenery": "The green life", + "beach": "Sand and sea", + "city": "In the city", + "moon": "In the moonlight", + "onTheRoad": "On the road again", + "food": "Culinary delight", + "pets": "Furry companions" +} diff --git a/mobile/lib/l10n/intl_es.arb b/mobile/lib/l10n/intl_es.arb index e6d9cdcfd6..ddae581d06 100644 --- a/mobile/lib/l10n/intl_es.arb +++ b/mobile/lib/l10n/intl_es.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_et.arb b/mobile/lib/l10n/intl_et.arb index 099579a313..5dc1464a5c 100644 --- a/mobile/lib/l10n/intl_et.arb +++ b/mobile/lib/l10n/intl_et.arb @@ -234,5 +234,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fa.arb b/mobile/lib/l10n/intl_fa.arb index fc92b92f46..691afcf2a0 100644 --- a/mobile/lib/l10n/intl_fa.arb +++ b/mobile/lib/l10n/intl_fa.arb @@ -324,5 +324,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/lib/l10n/intl_fr.arb index badfa74d54..987520c49d 100644 --- a/mobile/lib/l10n/intl_fr.arb +++ b/mobile/lib/l10n/intl_fr.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_gu.arb b/mobile/lib/l10n/intl_gu.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_gu.arb +++ b/mobile/lib/l10n/intl_gu.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_he.arb b/mobile/lib/l10n/intl_he.arb index dd5db1c0a0..f556b9425b 100644 --- a/mobile/lib/l10n/intl_he.arb +++ b/mobile/lib/l10n/intl_he.arb @@ -830,5 +830,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_hi.arb b/mobile/lib/l10n/intl_hi.arb index e52744a169..1ce96592d7 100644 --- a/mobile/lib/l10n/intl_hi.arb +++ b/mobile/lib/l10n/intl_hi.arb @@ -64,5 +64,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_hu.arb b/mobile/lib/l10n/intl_hu.arb index 7a28363da2..6de4b5fd03 100644 --- a/mobile/lib/l10n/intl_hu.arb +++ b/mobile/lib/l10n/intl_hu.arb @@ -26,5 +26,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_id.arb b/mobile/lib/l10n/intl_id.arb index e35d627651..a67cdb2e56 100644 --- a/mobile/lib/l10n/intl_id.arb +++ b/mobile/lib/l10n/intl_id.arb @@ -1146,5 +1146,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_it.arb b/mobile/lib/l10n/intl_it.arb index e97ea4e504..dddb3a4355 100644 --- a/mobile/lib/l10n/intl_it.arb +++ b/mobile/lib/l10n/intl_it.arb @@ -1615,5 +1615,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ja.arb b/mobile/lib/l10n/intl_ja.arb index d3f4b0d52c..a32237a62a 100644 --- a/mobile/lib/l10n/intl_ja.arb +++ b/mobile/lib/l10n/intl_ja.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_km.arb b/mobile/lib/l10n/intl_km.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_km.arb +++ b/mobile/lib/l10n/intl_km.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ko.arb b/mobile/lib/l10n/intl_ko.arb index a5ada71974..4f49a81b99 100644 --- a/mobile/lib/l10n/intl_ko.arb +++ b/mobile/lib/l10n/intl_ko.arb @@ -28,5 +28,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_lt.arb b/mobile/lib/l10n/intl_lt.arb index cd2ef1180d..4b6aff9088 100644 --- a/mobile/lib/l10n/intl_lt.arb +++ b/mobile/lib/l10n/intl_lt.arb @@ -1436,5 +1436,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ml.arb b/mobile/lib/l10n/intl_ml.arb index 35c7598463..cd66d1bd56 100644 --- a/mobile/lib/l10n/intl_ml.arb +++ b/mobile/lib/l10n/intl_ml.arb @@ -115,5 +115,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_nl.arb b/mobile/lib/l10n/intl_nl.arb index b6b6c439ad..55d2839001 100644 --- a/mobile/lib/l10n/intl_nl.arb +++ b/mobile/lib/l10n/intl_nl.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_no.arb b/mobile/lib/l10n/intl_no.arb index bc0e78bb61..ba108fb7ba 100644 --- a/mobile/lib/l10n/intl_no.arb +++ b/mobile/lib/l10n/intl_no.arb @@ -386,5 +386,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pl.arb b/mobile/lib/l10n/intl_pl.arb index 6b5d1f5eef..5e68d96be4 100644 --- a/mobile/lib/l10n/intl_pl.arb +++ b/mobile/lib/l10n/intl_pl.arb @@ -1613,5 +1613,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/lib/l10n/intl_pt.arb index efbf51cb82..4f9c533651 100644 --- a/mobile/lib/l10n/intl_pt.arb +++ b/mobile/lib/l10n/intl_pt.arb @@ -1690,5 +1690,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ro.arb b/mobile/lib/l10n/intl_ro.arb index 56c3fe5956..b38db6a294 100644 --- a/mobile/lib/l10n/intl_ro.arb +++ b/mobile/lib/l10n/intl_ro.arb @@ -1592,5 +1592,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ru.arb b/mobile/lib/l10n/intl_ru.arb index 36dbbc2147..4aa7617240 100644 --- a/mobile/lib/l10n/intl_ru.arb +++ b/mobile/lib/l10n/intl_ru.arb @@ -1477,5 +1477,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sl.arb b/mobile/lib/l10n/intl_sl.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_sl.arb +++ b/mobile/lib/l10n/intl_sl.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_sv.arb b/mobile/lib/l10n/intl_sv.arb index c5252f67c2..c9c6c009d7 100644 --- a/mobile/lib/l10n/intl_sv.arb +++ b/mobile/lib/l10n/intl_sv.arb @@ -555,5 +555,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ta.arb b/mobile/lib/l10n/intl_ta.arb index 5096949a13..5925e40925 100644 --- a/mobile/lib/l10n/intl_ta.arb +++ b/mobile/lib/l10n/intl_ta.arb @@ -31,5 +31,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_te.arb b/mobile/lib/l10n/intl_te.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_te.arb +++ b/mobile/lib/l10n/intl_te.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_th.arb b/mobile/lib/l10n/intl_th.arb index 405c109319..3ad3132ebb 100644 --- a/mobile/lib/l10n/intl_th.arb +++ b/mobile/lib/l10n/intl_th.arb @@ -311,5 +311,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_ti.arb b/mobile/lib/l10n/intl_ti.arb index 332450d365..a00b52609d 100644 --- a/mobile/lib/l10n/intl_ti.arb +++ b/mobile/lib/l10n/intl_ti.arb @@ -15,5 +15,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_tr.arb b/mobile/lib/l10n/intl_tr.arb index 60ee45be3a..d20ce04be4 100644 --- a/mobile/lib/l10n/intl_tr.arb +++ b/mobile/lib/l10n/intl_tr.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_uk.arb b/mobile/lib/l10n/intl_uk.arb index bcf868d353..f7104fb110 100644 --- a/mobile/lib/l10n/intl_uk.arb +++ b/mobile/lib/l10n/intl_uk.arb @@ -1600,5 +1600,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_vi.arb b/mobile/lib/l10n/intl_vi.arb index 07741c5f23..073e71b89b 100644 --- a/mobile/lib/l10n/intl_vi.arb +++ b/mobile/lib/l10n/intl_vi.arb @@ -1612,5 +1612,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/lib/l10n/intl_zh.arb index 2318c8cf0e..a7ec50b958 100644 --- a/mobile/lib/l10n/intl_zh.arb +++ b/mobile/lib/l10n/intl_zh.arb @@ -1689,5 +1689,7 @@ "photosKeepRelativeTimeDifference": "Photos keep relative time difference", "photocountPhotos": "$photoCount photos", "notThisPerson": "Not this person?", - "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library." + "selectedItemsWillBeRemovedFromThisPerson": "Selected items will be removed from this person, but not deleted from your library.", + "thisWeekThroughTheYears": "This week through the years", + "spotlightOnYourself": "Spotlight on yourself" } \ No newline at end of file diff --git a/mobile/lib/models/memories/clip_memory.dart b/mobile/lib/models/memories/clip_memory.dart index 7ec73aa5db..181920eb3b 100644 --- a/mobile/lib/models/memories/clip_memory.dart +++ b/mobile/lib/models/memories/clip_memory.dart @@ -1,9 +1,9 @@ +import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/models/memories/smart_memory.dart"; enum ClipMemoryType { sunrise, - sunset, mountains, greenery, beach, @@ -18,8 +18,6 @@ ClipMemoryType clipMemoryTypeFromString(String type) { switch (type) { case "sunrise": return ClipMemoryType.sunrise; - case "sunset": - return ClipMemoryType.sunset; case "mountains": return ClipMemoryType.mountains; case "greenery": @@ -44,9 +42,7 @@ ClipMemoryType clipMemoryTypeFromString(String type) { String clipQuery(ClipMemoryType clipMemoryType) { switch (clipMemoryType) { case ClipMemoryType.sunrise: - return "Photo of an absolutely stunning sunrise"; - case ClipMemoryType.sunset: - return "Photo of an absolutely stunning sunset"; + return "Photo of an absolutely stunning sunrise or sunset"; case ClipMemoryType.mountains: return "Photo of a beautiful mountain range"; case ClipMemoryType.greenery: @@ -66,28 +62,26 @@ String clipQuery(ClipMemoryType clipMemoryType) { } } -String clipTitle(ClipMemoryType clipMemoryType) { +String clipTitle(S s, ClipMemoryType clipMemoryType) { switch (clipMemoryType) { case ClipMemoryType.sunrise: - return "Sunrise"; - case ClipMemoryType.sunset: - return "Sunset"; + return s.sunrise; case ClipMemoryType.mountains: - return "Mountains"; + return s.mountains; case ClipMemoryType.greenery: - return "Greenery"; + return s.greenery; case ClipMemoryType.beach: - return "Beach"; + return s.beach; case ClipMemoryType.city: - return "City"; + return s.city; case ClipMemoryType.moon: - return "Moon"; + return s.moon; case ClipMemoryType.onTheRoad: - return "On the Road"; + return s.onTheRoad; case ClipMemoryType.food: - return "Food"; + return s.food; case ClipMemoryType.pets: - return "Pets"; + return s.pets; } } @@ -96,7 +90,6 @@ class ClipMemory extends SmartMemory { ClipMemory( List memories, - String title, int firstDateToShow, int lastDateToShow, this.clipMemoryType, { @@ -105,27 +98,13 @@ class ClipMemory extends SmartMemory { }) : super( memories, MemoryType.clip, - title, + '', firstDateToShow, lastDateToShow, ); - ClipMemory copyWith({ - List? memories, - String? title, - int? firstDateToShow, - int? lastDateToShow, - int? firstCreationTime, - int? lastCreationTime, - }) { - return ClipMemory( - memories ?? this.memories, - title ?? this.title, - firstDateToShow ?? this.firstDateToShow, - lastDateToShow ?? this.lastDateToShow, - clipMemoryType, - firstCreationTime: firstCreationTime ?? this.firstCreationTime, - lastCreationTime: lastCreationTime ?? this.lastCreationTime, - ); + @override + String createTitle(S s, String languageCode) { + return clipTitle(s, clipMemoryType); } } diff --git a/mobile/lib/models/memories/filler_memory.dart b/mobile/lib/models/memories/filler_memory.dart index 3e4e02b2c2..7fd597980c 100644 --- a/mobile/lib/models/memories/filler_memory.dart +++ b/mobile/lib/models/memories/filler_memory.dart @@ -1,10 +1,13 @@ +import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/models/memories/smart_memory.dart"; class FillerMemory extends SmartMemory { + // For creating the title + int yearsAgo; FillerMemory( List memories, - String title, + this.yearsAgo, int firstDateToShow, int lastDateToShow, { int? firstCreationTime, @@ -12,8 +15,13 @@ class FillerMemory extends SmartMemory { }) : super( memories, MemoryType.filler, - title, + 'filler', firstDateToShow, lastDateToShow, ); + + @override + String createTitle(S s, String languageCode) { + return s.yearsAgo(yearsAgo); + } } diff --git a/mobile/lib/models/memories/people_memory.dart b/mobile/lib/models/memories/people_memory.dart index 7902746eb3..83f6b1a363 100644 --- a/mobile/lib/models/memories/people_memory.dart +++ b/mobile/lib/models/memories/people_memory.dart @@ -1,3 +1,4 @@ +import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/models/memories/smart_memory.dart"; @@ -45,9 +46,9 @@ enum PeopleActivity { String activityQuery(PeopleActivity activity) { switch (activity) { case PeopleActivity.admiring: - return "Photo of two people lovingly admiring or looking at each other"; + return "Photo of two people admiring or looking at each other in a loving but non-intimate and non-physical way"; case PeopleActivity.embracing: - return "Photo of people hugging or embracing each other lovingly"; + return "Photo of people hugging or embracing each other lovingly, without inappropriately kissing or other intimate actions"; case PeopleActivity.party: return "Photo of people celebrating together"; case PeopleActivity.hiking: @@ -55,7 +56,7 @@ String activityQuery(PeopleActivity activity) { case PeopleActivity.feast: return "Photo of people having a big feast together"; case PeopleActivity.selfies: - return "Happy and nostalgic selfie with people"; + return "Happy and nostalgic selfie with people, clearly taken from the front camera of a phone"; case PeopleActivity.posing: return "Photo of people posing together in a funny manner for the camera"; case PeopleActivity.background: @@ -67,71 +68,104 @@ String activityQuery(PeopleActivity activity) { } } -String activityTitle(PeopleActivity activity, String personName) { +String activityTitle(S s, PeopleActivity activity, String personName) { switch (activity) { case PeopleActivity.admiring: - return "Admiring $personName"; + return s.admiringThem(personName); case PeopleActivity.embracing: - return "Embracing $personName"; + return s.embracingThem(personName); case PeopleActivity.party: - return "Party with $personName"; + return s.partyWithThem(personName); case PeopleActivity.hiking: - return "Hiking with $personName"; + return s.hikingWithThem(personName); case PeopleActivity.feast: - return "Feasting with $personName"; + return s.feastingWithThem(personName); case PeopleActivity.selfies: - return "Selfies with $personName"; + return s.selfiesWithThem(personName); case PeopleActivity.posing: - return "Posing with $personName"; + return s.posingWithThem(personName); case PeopleActivity.background: - return "You, $personName, and what a background!"; + return s.backgroundWithThem(personName); case PeopleActivity.sports: - return "Sports with $personName"; + return s.sportsWithThem(personName); case PeopleActivity.roadtrip: - return "Road trip with $personName"; + return s.roadtripWithThem(personName); } } class PeopleMemory extends SmartMemory { final String personID; final PeopleMemoryType peopleMemoryType; + final PeopleActivity? activity; + final String? personName; + final bool? isBirthday; + final int? newAge; PeopleMemory( List memories, - String title, int firstDateToShow, int lastDateToShow, this.peopleMemoryType, - this.personID, { + this.personID, + this.personName, { super.firstCreationTime, super.lastCreationTime, + this.activity, + this.isBirthday, + this.newAge, }) : super( memories, MemoryType.people, - title, + '', firstDateToShow, lastDateToShow, ); PeopleMemory copyWith({ - List? memories, - String? title, int? firstDateToShow, int? lastDateToShow, - PeopleMemoryType? peopleMemoryType, - String? personID, - int? firstCreationTime, - int? lastCreationTime, + bool? isBirthday, + int? newAge, }) { return PeopleMemory( - memories ?? this.memories, - title ?? this.title, + memories, firstDateToShow ?? this.firstDateToShow, lastDateToShow ?? this.lastDateToShow, - peopleMemoryType ?? this.peopleMemoryType, - personID ?? this.personID, - firstCreationTime: firstCreationTime ?? this.firstCreationTime, - lastCreationTime: lastCreationTime ?? this.lastCreationTime, + peopleMemoryType, + personID, + personName, + firstCreationTime: firstCreationTime, + lastCreationTime: lastCreationTime, + activity: activity, + isBirthday: isBirthday ?? this.isBirthday, + newAge: newAge ?? this.newAge, ); } + + @override + String createTitle(S s, String languageCode) { + switch (peopleMemoryType) { + case PeopleMemoryType.youAndThem: + assert(personName != null); + return s.youAndThem(personName!); + case PeopleMemoryType.doingSomethingTogether: + assert(activity != null); + assert(personName != null); + return activityTitle(s, activity!, personName!); + case PeopleMemoryType.spotlight: + if (personName == null) { + return s.spotlightOnYourself; + } else if (newAge == null) { + return s.spotlightOnThem(personName!); + } else { + if (isBirthday!) { + return s.personIsAge(personName!, newAge!); + } else { + return s.personTurningAge(personName!, newAge!); + } + } + case PeopleMemoryType.lastTimeYouSawThem: + return s.lastTimeWithThem(personName!); + } + } } diff --git a/mobile/lib/models/memories/smart_memory.dart b/mobile/lib/models/memories/smart_memory.dart index 030d4fd8a2..b05ff64959 100644 --- a/mobile/lib/models/memories/smart_memory.dart +++ b/mobile/lib/models/memories/smart_memory.dart @@ -1,3 +1,4 @@ +import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; enum MemoryType { @@ -56,6 +57,10 @@ class SmartMemory { return now >= firstDateToShow && now <= lastDateToShow; } + String createTitle(S s, String languageCode) { + throw UnimplementedError("createTitle must be implemented in subclass"); + } + int averageCreationTime() { if (firstCreationTime != null && lastCreationTime != null) { return (firstCreationTime! + lastCreationTime!) ~/ 2; diff --git a/mobile/lib/models/memories/time_memory.dart b/mobile/lib/models/memories/time_memory.dart index 7dafef90ec..e723224aba 100644 --- a/mobile/lib/models/memories/time_memory.dart +++ b/mobile/lib/models/memories/time_memory.dart @@ -1,19 +1,53 @@ +import "package:intl/intl.dart"; +import "package:photos/generated/l10n.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/models/memories/smart_memory.dart"; class TimeMemory extends SmartMemory { + // For computing the title + DateTime? day; + DateTime? month; + int? yearsAgo; + TimeMemory( List memories, - String title, int firstDateToShow, int lastDateToShow, { + this.day, + this.month, + this.yearsAgo, int? firstCreationTime, int? lastCreationTime, }) : super( memories, MemoryType.time, - title, + '', firstDateToShow, lastDateToShow, ); + + @override + String createTitle(S s, String languageCode) { + if (day != null) { + final dayFormat = DateFormat.MMMd(languageCode).format(day!); + if (yearsAgo != null) { + return "$dayFormat, " + s.yearsAgo(yearsAgo!); + } else { + return s.throughTheYears(dayFormat); + } + } + if (month != null) { + final monthFormat = DateFormat.MMMM(languageCode).format(month!); + if (yearsAgo != null) { + return "$monthFormat, " + s.yearsAgo(yearsAgo!); + } else { + return s.throughTheYears(monthFormat); + } + } + if (yearsAgo != null) { + return s.thisWeekXYearsAgo(yearsAgo!); + } else { + return s.thisWeekThroughTheYears; + } + } } diff --git a/mobile/lib/models/memories/trip_memory.dart b/mobile/lib/models/memories/trip_memory.dart index 8763d20c1e..43480ded3b 100644 --- a/mobile/lib/models/memories/trip_memory.dart +++ b/mobile/lib/models/memories/trip_memory.dart @@ -1,3 +1,4 @@ +import "package:photos/generated/l10n.dart"; import "package:photos/models/location/location.dart"; import "package:photos/models/memories/memory.dart"; import "package:photos/models/memories/smart_memory.dart"; @@ -5,39 +6,60 @@ import "package:photos/models/memories/smart_memory.dart"; class TripMemory extends SmartMemory { final Location location; + // Stuff for the title + String? locationName; + int? tripYear; + TripMemory( List memories, - String title, int firstDateToShow, int lastDateToShow, this.location, { + this.locationName, + this.tripYear, super.firstCreationTime, super.lastCreationTime, }) : super( memories, MemoryType.trips, - title, + '', firstDateToShow, lastDateToShow, ); TripMemory copyWith({ List? memories, - String? title, int? firstDateToShow, int? lastDateToShow, - Location? location, - int? firstCreationTime, - int? lastCreationTime, + String? locationName, + int? tripYear, }) { return TripMemory( memories ?? this.memories, - title ?? this.title, firstDateToShow ?? this.firstDateToShow, lastDateToShow ?? this.lastDateToShow, - location ?? this.location, - firstCreationTime: firstCreationTime ?? this.firstCreationTime, - lastCreationTime: lastCreationTime ?? this.lastCreationTime, + location, + locationName: locationName ?? this.locationName, + tripYear: tripYear ?? this.tripYear, + firstCreationTime: firstCreationTime, + lastCreationTime: lastCreationTime, ); } + + @override + String createTitle(S s, String languageCode) { + assert(locationName != null || tripYear != null); + if (locationName != null) { + if (locationName!.toLowerCase().contains("base")) return locationName!; + return s.tripToLocation(locationName!); + } + if (tripYear != null) { + if (tripYear == DateTime.now().year - 1) { + return s.lastYearsTrip; + } else { + return s.tripInYear(tripYear!); + } + } + throw ArgumentError("TripMemory must have a location name or trip year"); + } } diff --git a/mobile/lib/services/memories_cache_service.dart b/mobile/lib/services/memories_cache_service.dart index 4d6e0da42c..481b3b8fe4 100644 --- a/mobile/lib/services/memories_cache_service.dart +++ b/mobile/lib/services/memories_cache_service.dart @@ -143,7 +143,7 @@ class MemoriesCacheService { try { if ((!_shouldUpdate && !forced) || _isUpdateInProgress) { _logger.info( - "No update needed as shouldUpdate: $_shouldUpdate, forced: $forced and isUpdateInProgress $_isUpdateInProgress", + "No update needed (shouldUpdate: $_shouldUpdate, forced: $forced, isUpdateInProgress $_isUpdateInProgress)", ); if (_isUpdateInProgress) { int waitingTime = 0; @@ -154,7 +154,9 @@ class MemoriesCacheService { } return; } - _logger.info("updating memories cache"); + _logger.info( + "Updating memories cache (shouldUpdate: $_shouldUpdate, forced: $forced, isUpdateInProgress $_isUpdateInProgress)", + ); _isUpdateInProgress = true; final EnteWatch? w = kDebugMode ? EnteWatch("MemoriesCacheService") : null; diff --git a/mobile/lib/services/smart_memories_service.dart b/mobile/lib/services/smart_memories_service.dart index dfefe1cbdc..21d1a8ef14 100644 --- a/mobile/lib/services/smart_memories_service.dart +++ b/mobile/lib/services/smart_memories_service.dart @@ -13,6 +13,8 @@ import "package:photos/core/constants.dart"; import "package:photos/db/memories_db.dart"; import "package:photos/db/ml/db.dart"; import "package:photos/extensions/stop_watch.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/l10n/l10n.dart"; import "package:photos/models/base_location.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/location/location.dart"; @@ -111,11 +113,13 @@ class SmartMemoriesService { } _logger.finest('clipPositiveTextVector and clipPeopleActivityVectors $t'); - // final locale = await getLocale(); - // TODO: lau: locale with DateFormat is not working well in computer, fix later + final local = await getLocale(); + final languageCode = local?.languageCode ?? "en"; + final s = await S.load(local!); + _logger.finest('get locale and S $t'); _logger.finest('all data fetched $t at ${DateTime.now()}, to computer'); - return Computer.shared().compute( + final memoriesResult = await Computer.shared().compute( _allMemoriesCalculations, param: { "allFiles": allFiles, @@ -133,7 +137,11 @@ class SmartMemoriesService { "clipPeopleActivityVectors": clipPeopleActivityVectors, "clipMemoryTypeVectors": clipMemoryTypeVectors, }, - ); + ) as MemoriesResult; + for (final memory in memoriesResult.memories) { + memory.title = memory.createTitle(s, languageCode); + } + return memoriesResult; } catch (e, s) { _logger.severe("Error calculating smart memories", e, s); return MemoriesResult([], []); @@ -210,6 +218,7 @@ class SmartMemoriesService { } dev.log('arguments from indirect data calculated $t'); dev.log('starting actual memory calculations ${DateTime.now()}'); + dev.log("All files length at start: ${allFiles.length} $t"); final List memories = []; @@ -333,8 +342,7 @@ class SmartMemoriesService { currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch; w?.log('allFiles setup'); - // Get ordered list of important people (all named, from most to least files) - if (persons.length < 5) return []; // Stop if not enough named persons + // Get ordered (random) list of important people final personIdToPerson = {}; final personIdToFaceIDs = >{}; final personIdToFileIDs = >{}; @@ -355,11 +363,7 @@ class SmartMemoriesService { .where((person) => !person.data.isHidden) .map((p) => p.remoteID) .toList(); - orderedImportantPersonsID.sort((a, b) { - final aFaces = personIdToFaceIDs[a]!.length; - final bFaces = personIdToFaceIDs[b]!.length; - return bFaces.compareTo(aFaces); - }); + orderedImportantPersonsID.shuffle(Random()); w?.log('orderedImportantPersonsID setup'); // Check if the user has assignmed "me" @@ -393,10 +397,6 @@ class SmartMemoriesService { } } if (spotlightFiles.length > 5) { - String title = "Spotlight on $personName"; - if (isMeAssigned && meID == personID) { - title = "Spotlight on yourself"; - } final selectSpotlightMemories = await _bestSelectionPeople( spotlightFiles.map((f) => Memory.fromFile(f, seenTimes)).toList(), fileIDToImageEmbedding: fileIDToImageEmbedding, @@ -404,11 +404,11 @@ class SmartMemoriesService { ); final spotlightMemory = PeopleMemory( selectSpotlightMemories, - title, nowInMicroseconds, windowEnd, PeopleMemoryType.spotlight, personID, + (isMeAssigned && meID == personID) ? null : personName, ); personToMemories .putIfAbsent(personID, () => {}) @@ -429,7 +429,7 @@ class SmartMemoriesService { } } if (youAndThemFiles.length > 5) { - final String title = "You and $personName"; + // final String title = "You and $personName"; final selectYouAndThemMemories = await _bestSelectionPeople( youAndThemFiles.map((f) => Memory.fromFile(f, seenTimes)).toList(), fileIDToImageEmbedding: fileIDToImageEmbedding, @@ -437,11 +437,11 @@ class SmartMemoriesService { ); final youAndThemMemory = PeopleMemory( selectYouAndThemMemories, - title, nowInMicroseconds, windowEnd, PeopleMemoryType.youAndThem, personID, + personName, ); personToMemories.putIfAbsent(personID, () => {}).putIfAbsent( PeopleMemoryType.youAndThem, @@ -485,7 +485,6 @@ class SmartMemoriesService { } } if (activityFiles.length > 5) { - final String title = activityTitle(activity, personName); final selectActivityMemories = await _bestSelectionPeople( activityFiles.map((f) => Memory.fromFile(f, seenTimes)).toList(), fileIDToImageEmbedding: fileIDToImageEmbedding, @@ -493,11 +492,12 @@ class SmartMemoriesService { ); final activityMemory = PeopleMemory( selectActivityMemories, - title, nowInMicroseconds, windowEnd, PeopleMemoryType.doingSomethingTogether, personID, + personName, + activity: activity, ); personToMemories .putIfAbsent(personID, () => {}) @@ -540,16 +540,15 @@ class SmartMemoriesService { } } if (longAgo && lastTimeYouSawThemFiles.length >= 2 && meID != personID) { - final String title = "Last time with $personName"; final lastTimeMemory = PeopleMemory( lastTimeYouSawThemFiles .map((f) => Memory.fromFile(f, seenTimes)) .toList(), - title, nowInMicroseconds, windowEnd, PeopleMemoryType.lastTimeYouSawThem, personID, + personName, lastCreationTime: lastCreationTime, ); personToMemories.putIfAbsent(personID, () => {}).putIfAbsent( @@ -583,17 +582,15 @@ class SmartMemoriesService { DateTime(currentTime.year, birthdate.month, birthdate.day); final daysTillBirthday = thisBirthday.difference(currentTime).inDays; if (daysTillBirthday < 7 && daysTillBirthday >= 0) { - final personName = person.data.name; final int newAge = currentTime.year - birthdate.year; final spotlightMem = personMemories[PeopleMemoryType.spotlight]?.first; - if (spotlightMem != null) { - final String firstTitle = "$personName turning $newAge!"; - final String secondTitle = "$personName is $newAge!"; + if (spotlightMem != null && spotlightMem.personName != null) { final thisBirthday = birthdate.copyWith(year: currentTime.year); memoryResults.add( spotlightMem.copyWith( - title: firstTitle, + isBirthday: false, + newAge: newAge, firstDateToShow: thisBirthday .subtract(const Duration(days: 6)) .microsecondsSinceEpoch, @@ -602,7 +599,8 @@ class SmartMemoriesService { ); memoryResults.add( spotlightMem.copyWith( - title: secondTitle, + isBirthday: true, + newAge: newAge, firstDateToShow: thisBirthday.microsecondsSinceEpoch, lastDateToShow: thisBirthday.add(kDayItself).microsecondsSinceEpoch, @@ -746,7 +744,6 @@ class SmartMemoriesService { }); clipTypeToMemory[clipMemoryType] = ClipMemory( clipFiles.take(10).map((f) => Memory.fromFile(f, seenTimes)).toList(), - clipTitle(clipMemoryType), nowInMicroseconds, windowEnd, clipMemoryType, @@ -764,7 +761,7 @@ class SmartMemoriesService { // Loop through the clip types and add based on rotation clipMemoriesLoop: - for (final clipMemoryType in ClipMemoryType.values) { + for (final clipMemoryType in ClipMemoryType.values..shuffle()) { final clipMemory = clipTypeToMemory[clipMemoryType]; if (clipMemory == null) continue; for (final shownLog in shownClip) { @@ -941,7 +938,6 @@ class SmartMemoriesService { currentBlockFiles, seenTimes, ), - 'Trip1', 0, 0, location, @@ -968,7 +964,6 @@ class SmartMemoriesService { tripLocations.add( TripMemory( Memory.fromFiles(currentBlockFiles, seenTimes), - 'Trip2', 0, 0, location, @@ -1002,7 +997,6 @@ class SmartMemoriesService { )) { mergedTrips[idx] = TripMemory( otherTrip.memories + trip.memories, - 'Trip3', 0, 0, otherTrip.location, @@ -1020,7 +1014,6 @@ class SmartMemoriesService { mergedTrips.add( TripMemory( trip.memories, - 'Trip4', 0, 0, trip.location, @@ -1057,10 +1050,10 @@ class SmartMemoriesService { memoryResults.add( TripMemory( Memory.fromFiles(baseLocation.files, seenTimes), - name, nowInMicroseconds, windowEnd, baseLocation.location, + locationName: name, ), ); } @@ -1070,12 +1063,6 @@ class SmartMemoriesService { ).year; final String? locationName = _tryFindLocationName(trip.memories, cities); - String name = "Trip in $year"; - if (locationName != null) { - name = "Trip to $locationName"; - } else if (year == currentTime.year - 1) { - name = "Last year's trip"; - } final photoSelection = await _bestSelection( trip.memories, fileIdToFaces: fileIdToFaces, @@ -1086,7 +1073,8 @@ class SmartMemoriesService { memoryResults.add( trip.copyWith( memories: photoSelection, - title: name, + tripYear: year, + locationName: locationName, firstDateToShow: nowInMicroseconds, lastDateToShow: windowEnd, ), @@ -1129,13 +1117,6 @@ class SmartMemoriesService { .year; final String? locationName = _tryFindLocationName(trip.memories, cities); - String name = - "Trip in $year"; // TODO lau: extract strings for translation - if (locationName != null) { - name = "Trip to $locationName"; - } else if (year == currentTime.year - 1) { - name = "Last year's trip"; - } final photoSelection = await _bestSelection( trip.memories, fileIdToFaces: fileIdToFaces, @@ -1162,7 +1143,8 @@ class SmartMemoriesService { memoryResults.add( trip.copyWith( memories: photoSelection, - title: name, + tripYear: year, + locationName: locationName, firstDateToShow: firstDateToShow, lastDateToShow: lastDateToShow, ), @@ -1206,12 +1188,6 @@ class SmartMemoriesService { ).year; final String? locationName = _tryFindLocationName(trip.memories, cities); - String name = "Trip in $year"; - if (locationName != null) { - name = "Trip to $locationName"; - } else if (year == currentTime.year - 1) { - name = "Last year's trip"; - } final photoSelection = await _bestSelection( trip.memories, fileIdToFaces: fileIdToFaces, @@ -1222,7 +1198,8 @@ class SmartMemoriesService { memoryResults.add( trip.copyWith( memories: photoSelection, - title: name, + tripYear: year, + locationName: locationName, firstDateToShow: nowInMicroseconds, lastDateToShow: windowEnd, ), @@ -1302,7 +1279,7 @@ class SmartMemoriesService { memoryResult.add( TimeMemory( photoSelection, - "${DateFormat.MMMd().format(date)} through the years", + day: date, date.subtract(kMemoriesMargin).microsecondsSinceEpoch, date.add(kDayItself).microsecondsSinceEpoch, ), @@ -1321,12 +1298,11 @@ class SmartMemoriesService { fileIDToImageEmbedding: fileIDToImageEmbedding, clipPositiveTextVector: clipPositiveTextVector, ); - final name = - "${DateFormat.MMMd().format(date)}, ${currentTime.year - date.year} years ago"; memoryResult.add( TimeMemory( photoSelection, - name, + day: date, + yearsAgo: currentTime.year - date.year, showDate.subtract(kMemoriesMargin).microsecondsSinceEpoch, showDate.add(kDayItself).microsecondsSinceEpoch, ), @@ -1370,11 +1346,10 @@ class SmartMemoriesService { fileIDToImageEmbedding: fileIDToImageEmbedding, clipPositiveTextVector: clipPositiveTextVector, ); - const name = "This week through the years"; + // const name = "This week through the years"; memoryResult.add( TimeMemory( photoSelection, - name, currentTime.subtract(kMemoriesMargin).microsecondsSinceEpoch, currentTime.add(kMemoriesUpdateFrequency).microsecondsSinceEpoch, ), @@ -1393,12 +1368,10 @@ class SmartMemoriesService { fileIDToImageEmbedding: fileIDToImageEmbedding, clipPositiveTextVector: clipPositiveTextVector, ); - final name = "This week, ${currentTime.year - date.year} years ago"; - memoryResult.add( TimeMemory( photoSelection, - name, + yearsAgo: currentTime.year - date.year, currentTime.subtract(kMemoriesMargin).microsecondsSinceEpoch, currentTime .add(kMemoriesUpdateFrequency) @@ -1448,14 +1421,13 @@ class SmartMemoriesService { fileIDToImageEmbedding: fileIDToImageEmbedding, clipPositiveTextVector: clipPositiveTextVector, ); - final monthName = DateFormat.MMMM().format(DateTime(year, currentMonth)); final daysLeftInMonth = DateTime(currentYear, currentMonth + 1, 0).day - currentTime.day + 1; - final name = monthName + ", ${currentTime.year - year} years ago"; memoryResult.add( TimeMemory( photoSelection, - name, + month: DateTime(year, currentMonth), + yearsAgo: currentTime.year - year, currentTime.microsecondsSinceEpoch, currentTime .add(Duration(days: daysLeftInMonth)) @@ -1476,15 +1448,12 @@ class SmartMemoriesService { fileIDToImageEmbedding: fileIDToImageEmbedding, clipPositiveTextVector: clipPositiveTextVector, ); - final monthName = - DateFormat.MMMM().format(DateTime(currentTime.year, currentMonth)); final daysLeftInMonth = DateTime(currentYear, currentMonth + 1, 0).day - currentTime.day + 1; - final name = monthName + " through the years"; memoryResult.add( TimeMemory( photoSelection, - name, + month: DateTime(currentYear, currentMonth), currentTime.microsecondsSinceEpoch, currentTime.add(Duration(days: daysLeftInMonth)).microsecondsSinceEpoch, ), @@ -1542,7 +1511,7 @@ class SmartMemoriesService { ); final fillerMemory = FillerMemory( memories, - "filler", + yearAgo, nowInMicroseconds, windowEnd, ); diff --git a/mobile/lib/services/sync/local_sync_service.dart b/mobile/lib/services/sync/local_sync_service.dart index a0bc54437e..aa630c2d73 100644 --- a/mobile/lib/services/sync/local_sync_service.dart +++ b/mobile/lib/services/sync/local_sync_service.dart @@ -296,10 +296,12 @@ class LocalSyncService { files, conflictAlgorithm: SqliteAsyncConflictAlgorithm.ignore, ); - _logger.info('Inserted ${files.length} files'); - Bus.instance.fire( - LocalPhotosUpdatedEvent(allFiles, source: "loadedPhoto"), - ); + _logger.info('Inserted ${files.length} out of ${allFiles.length} files'); + if (allFiles.isNotEmpty) { + Bus.instance.fire( + LocalPhotosUpdatedEvent(allFiles, source: "loadedPhoto"), + ); + } } await _prefs.setInt(kDbUpdationTimeKey, toTime); } diff --git a/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart b/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart index 46acde870d..cec969abba 100644 --- a/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart +++ b/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart @@ -299,6 +299,26 @@ class _MLDebugSectionWidgetState extends State { }, ), sectionOptionSpacing, + MenuItemWidget( + captionedTextWidget: const CaptionedTextWidget( + title: "Clear memories cache", + ), + pressedColor: getEnteColorScheme(context).fillFaint, + trailingIcon: Icons.chevron_right_outlined, + trailingIconIsMuted: true, + onTap: () async { + try { + final now = DateTime.now(); + await memoriesCacheService.clearMemoriesCache(); + final duration = DateTime.now().difference(now); + showShortToast(context, "Done in ${duration.inSeconds} seconds"); + } catch (e, s) { + logger.warning('Clear memories cache failed', e, s); + await showGenericErrorDialog(context: context, error: e); + } + }, + ), + sectionOptionSpacing, MenuItemWidget( captionedTextWidget: const CaptionedTextWidget( title: "Sync person mappings ", diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index ba6939fb20..4933d53356 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -16,7 +16,7 @@ import { authTheme } from "@/base/components/utils/theme"; import { BaseContext, deriveBaseContext } from "@/base/context"; import { logStartupBanner } from "@/base/log-web"; import HTTPService from "@ente/shared/network/HTTPService"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import "@fontsource-variable/inter"; import { CssBaseline } from "@mui/material"; @@ -33,7 +33,7 @@ const App: React.FC = ({ Component, pageProps }) => { const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog(); useEffect(() => { - const user = getData(LS_KEYS.USER) as User | undefined | null; + const user = getData("user") as User | undefined | null; logStartupBanner(user?.id); HTTPService.setHeaders({ "X-Client-Package": clientPackageName }); }, []); diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx index eb4ddee58a..597fe6f103 100644 --- a/web/apps/auth/src/pages/auth.tsx +++ b/web/apps/auth/src/pages/auth.tsx @@ -12,7 +12,6 @@ import { useBaseContext } from "@/base/context"; import { isHTTP401Error } from "@/base/http"; import log from "@/base/log"; import { masterKeyFromSessionIfLoggedIn } from "@/base/session"; -import { AUTH_PAGES as PAGES } from "@ente/shared/constants/pages"; import LogoutOutlinedIcon from "@mui/icons-material/LogoutOutlined"; import { Box, @@ -42,7 +41,7 @@ const Page: React.FC = () => { const fetchCodes = async () => { const masterKey = await masterKeyFromSessionIfLoggedIn(); if (!masterKey) { - stashRedirect(PAGES.AUTH); + stashRedirect("/auth"); void router.push("/"); return; } diff --git a/web/apps/auth/src/pages/index.tsx b/web/apps/auth/src/pages/index.tsx index c3d16a7ce9..b3c524edf9 100644 --- a/web/apps/auth/src/pages/index.tsx +++ b/web/apps/auth/src/pages/index.tsx @@ -1,10 +1,9 @@ -import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { useRouter } from "next/router"; import React, { useEffect } from "react"; const Page: React.FC = () => { const router = useRouter(); - useEffect(() => void router.push(PAGES.LOGIN), [router]); + useEffect(() => void router.push("/login"), [router]); return <>; }; diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index 08dae2728a..7788d8190a 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -9,7 +9,7 @@ import log from "@/base/log"; import VerifyMasterPasswordForm, { type VerifyMasterPasswordFormProps, } from "@ente/shared/components/VerifyMasterPasswordForm"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; import { t } from "i18next"; import { useCallback, useEffect, useState } from "react"; @@ -60,12 +60,12 @@ export const AuthenticateUser: React.FC = ({ useEffect(() => { const main = async () => { try { - const user = getData(LS_KEYS.USER); + const user = getData("user"); if (!user) { throw Error("User not found"); } setUser(user); - const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes = getData("keyAttributes"); if ( (!user?.token && !user?.encryptedToken) || (keyAttributes && !keyAttributes.memLimit) diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 72730911b4..540330ff51 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -25,7 +25,7 @@ import type { PublicURL, UpdatePublicURL, } from "@/media/collection"; -import { COLLECTION_ROLE, type CollectionUser } from "@/media/collection"; +import { type CollectionUser } from "@/media/collection"; import { PublicLinkCreated } from "@/new/photos/components/share/PublicLinkCreated"; import { avatarTextColor } from "@/new/photos/services/avatar"; import type { CollectionSummary } from "@/new/photos/services/collection/ui"; @@ -153,12 +153,12 @@ function SharingDetails({ collection, type }) { : collection.owner?.email; const collaborators = collection.sharees - ?.filter((sharee) => sharee.role === COLLECTION_ROLE.COLLABORATOR) + ?.filter((sharee) => sharee.role == "COLLABORATOR") .map((sharee) => sharee.email); const viewers = collection.sharees - ?.filter((sharee) => sharee.role === COLLECTION_ROLE.VIEWER) + ?.filter((sharee) => sharee.role == "VIEWER") .map((sharee) => sharee.email) || []; const isOwner = galleryContext.user?.id === collection.owner?.id; @@ -345,17 +345,15 @@ const EmailShare: React.FC = ({ collection, onRootClose }) => { const closeManageEmailShare = () => setManageEmailShareView(false); const openManageEmailShare = () => setManageEmailShareView(true); - const participantType = useRef< - COLLECTION_ROLE.COLLABORATOR | COLLECTION_ROLE.VIEWER - >(undefined); + const participantType = useRef<"COLLABORATOR" | "VIEWER">(undefined); const openAddCollab = () => { - participantType.current = COLLECTION_ROLE.COLLABORATOR; + participantType.current = "COLLABORATOR"; openAddParticipant(); }; const openAddViewer = () => { - participantType.current = COLLECTION_ROLE.VIEWER; + participantType.current = "VIEWER"; openAddParticipant(); }; @@ -469,7 +467,7 @@ interface AddParticipantProps { open: boolean; onClose: () => void; onRootClose: () => void; - type: COLLECTION_ROLE.VIEWER | COLLECTION_ROLE.COLLABORATOR; + type: "VIEWER" | "COLLABORATOR"; } const AddParticipant: React.FC = ({ @@ -541,7 +539,7 @@ const AddParticipant: React.FC = ({ {...{ onClose }} onRootClose={handleRootClose} title={ - type === COLLECTION_ROLE.VIEWER + type == "VIEWER" ? t("add_viewers") : t("add_collaborators") } @@ -554,7 +552,7 @@ const AddParticipant: React.FC = ({ placeholder={t("enter_email")} fieldType="email" buttonText={ - type === COLLECTION_ROLE.VIEWER + type == "VIEWER" ? t("add_viewers") : t("add_collaborators") } @@ -818,19 +816,17 @@ const ManageEmailShare: React.FC = ({ const closeAddParticipant = () => setAddParticipantView(false); const openAddParticipant = () => setAddParticipantView(true); - const participantType = useRef< - COLLECTION_ROLE.COLLABORATOR | COLLECTION_ROLE.VIEWER - >(null); + const participantType = useRef<"COLLABORATOR" | "VIEWER">(null); const selectedParticipant = useRef(null); const openAddCollab = () => { - participantType.current = COLLECTION_ROLE.COLLABORATOR; + participantType.current = "COLLABORATOR"; openAddParticipant(); }; const openAddViewer = () => { - participantType.current = COLLECTION_ROLE.VIEWER; + participantType.current = "VIEWER"; openAddParticipant(); }; @@ -857,12 +853,12 @@ const ManageEmailShare: React.FC = ({ const isOwner = galleryContext.user.id === collection.owner?.id; const collaborators = collection.sharees - ?.filter((sharee) => sharee.role === COLLECTION_ROLE.COLLABORATOR) + ?.filter((sharee) => sharee.role == "COLLABORATOR") .map((sharee) => sharee.email); const viewers = collection.sharees - ?.filter((sharee) => sharee.role === COLLECTION_ROLE.VIEWER) + ?.filter((sharee) => sharee.role == "VIEWER") .map((sharee) => sharee.email) || []; const openManageParticipant = (email) => { diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 119c4f7107..b0000ec873 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -15,15 +15,11 @@ import { type CollectionSummaries, } from "@/new/photos/services/collection/ui"; import { includes } from "@/utils/type-guards"; -import { - getData, - LS_KEYS, - removeData, -} from "@ente/shared/storage/localStorage"; +import { getData, removeData } from "@ente/shared/storage/localStorage"; import { AllAlbums } from "components/Collections/AllAlbums"; import { SetCollectionNamerAttributes } from "components/Collections/CollectionNamer"; import { CollectionShare } from "components/Collections/CollectionShare"; -import { ITEM_TYPE, TimeStampListItem } from "components/FileList"; +import { TimeStampListItem } from "components/FileList"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { sortCollectionSummaries } from "services/collectionService"; import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; @@ -167,7 +163,7 @@ export const GalleryBarAndListHeader: React.FC = ({ ) : ( <> ), - itemType: ITEM_TYPE.HEADER, + tag: "header", height: 68, }); }, [ @@ -252,7 +248,7 @@ const useCollectionsSortByLocalState = (initialValue: CollectionsSortBy) => { // // This migration added Sep 2024, can be removed after a bit (esp // since it effectively runs on each app start). (tag: Migration). - const oldData = getData(LS_KEYS.COLLECTION_SORT_BY); + const oldData = getData("collectionSortBy"); if (oldData) { let newValue: CollectionsSortBy | undefined; switch (oldData.value) { @@ -270,7 +266,7 @@ const useCollectionsSortByLocalState = (initialValue: CollectionsSortBy) => { localStorage.setItem(key, newValue); setValue(newValue); } - removeData(LS_KEYS.COLLECTION_SORT_BY); + removeData("collectionSortBy"); } } }, []); diff --git a/web/apps/photos/src/components/Export.tsx b/web/apps/photos/src/components/Export.tsx index 1fe4708d79..3864dc02e2 100644 --- a/web/apps/photos/src/components/Export.tsx +++ b/web/apps/photos/src/components/Export.tsx @@ -51,7 +51,9 @@ export const Export: React.FC = ({ allCollectionsNameByID, }) => { const { showMiniDialog } = useBaseContext(); - const [exportStage, setExportStage] = useState(ExportStage.INIT); + const [exportStage, setExportStage] = useState( + ExportStage.init, + ); const [exportFolder, setExportFolder] = useState(""); const [continuousExport, setContinuousExport] = useState(false); const [exportProgress, setExportProgress] = useState({ @@ -224,8 +226,8 @@ function ExportDirectory({ exportFolder, changeExportDirectory, exportStage }) { {exportFolder ? ( <> - {exportStage === ExportStage.FINISHED || - exportStage === ExportStage.INIT ? ( + {exportStage === ExportStage.finished || + exportStage === ExportStage.init ? ( @@ -303,15 +305,15 @@ const ExportDynamicContent = ({ allCollectionsNameByID: Map; }) => { switch (exportStage) { - case ExportStage.INIT: + case ExportStage.init: return ; - case ExportStage.MIGRATION: - case ExportStage.STARTING: - case ExportStage.EXPORTING_FILES: - case ExportStage.RENAMING_COLLECTION_FOLDERS: - case ExportStage.TRASHING_DELETED_FILES: - case ExportStage.TRASHING_DELETED_COLLECTIONS: + case ExportStage.migration: + case ExportStage.starting: + case ExportStage.exportingFiles: + case ExportStage.renamingCollectionFolders: + case ExportStage.trashingDeletedFiles: + case ExportStage.trashingDeletedCollections: return ( ); - case ExportStage.FINISHED: + case ExportStage.finished: return ( { return ( - props.exportStage === ExportStage.STARTING || - props.exportStage === ExportStage.MIGRATION || - props.exportStage === ExportStage.RENAMING_COLLECTION_FOLDERS || - props.exportStage === ExportStage.TRASHING_DELETED_FILES || - props.exportStage === ExportStage.TRASHING_DELETED_COLLECTIONS + props.exportStage === ExportStage.starting || + props.exportStage === ExportStage.migration || + props.exportStage === ExportStage.renamingCollectionFolders || + props.exportStage === ExportStage.trashingDeletedFiles || + props.exportStage === ExportStage.trashingDeletedCollections ); }; return ( @@ -35,18 +35,18 @@ export default function ExportInProgress(props: Props) { - {props.exportStage === ExportStage.STARTING ? ( + {props.exportStage === ExportStage.starting ? ( t("export_starting") - ) : props.exportStage === ExportStage.MIGRATION ? ( + ) : props.exportStage === ExportStage.migration ? ( t("preparing") ) : props.exportStage === - ExportStage.RENAMING_COLLECTION_FOLDERS ? ( + ExportStage.renamingCollectionFolders ? ( t("renaming_album_folders") ) : props.exportStage === - ExportStage.TRASHING_DELETED_FILES ? ( + ExportStage.trashingDeletedFiles ? ( t("trashing_deleted_files") ) : props.exportStage === - ExportStage.TRASHING_DELETED_COLLECTIONS ? ( + ExportStage.trashingDeletedCollections ? ( t("trashing_deleted_albums") ) : ( = ({ useEffect(() => { setTimeStampList((timeStampList) => { timeStampList = timeStampList ?? []; - const hasHeader = - timeStampList.length > 0 && - timeStampList[0].itemType === ITEM_TYPE.HEADER; - + const hasHeader = timeStampList[0]?.tag == "header"; if (hasHeader) { return timeStampList; } @@ -289,8 +283,8 @@ export const FileList: React.FC = ({ timeStampList = timeStampList ?? []; const hasFooter = timeStampList.length > 0 && - timeStampList[timeStampList.length - 1].itemType === - ITEM_TYPE.MARKETING_FOOTER; + timeStampList[timeStampList.length - 1]?.tag == + "publicAlbumsFooter"; if (hasFooter) { return timeStampList; } @@ -334,12 +328,12 @@ export const FileList: React.FC = ({ currentDate = item.file.metadata.creationTime / 1000; timeStampList.push({ - itemType: ITEM_TYPE.TIME, + tag: "date", date: item.timelineDateString, id: currentDate.toString(), }); timeStampList.push({ - itemType: ITEM_TYPE.FILE, + tag: "file", items: [item], itemStartIndex: index, }); @@ -350,7 +344,7 @@ export const FileList: React.FC = ({ } else { listItemIndex = 1; timeStampList.push({ - itemType: ITEM_TYPE.FILE, + tag: "file", items: [item], itemStartIndex: index, }); @@ -367,7 +361,7 @@ export const FileList: React.FC = ({ } else { listItemIndex = 1; timeStampList.push({ - itemType: ITEM_TYPE.FILE, + tag: "file", items: [item], itemStartIndex: index, }); @@ -399,7 +393,6 @@ export const FileList: React.FC = ({ const getEmptyListItem = () => { return { - itemType: ITEM_TYPE.OTHER, item: ( @@ -433,104 +426,99 @@ export const FileList: React.FC = ({ return sum; })(); return { - itemType: ITEM_TYPE.OTHER, item: <>, height: Math.max(height - fileListHeight - footerHeight, 0), }; }; - const getAppDownloadFooter = () => { - return { - itemType: ITEM_TYPE.MARKETING_FOOTER, - height: FOOTER_HEIGHT, - item: ( - - - - ), - b: ( - - ), - }} - /> - - - ), - }; - }; + const getAppDownloadFooter = (): TimeStampListItem => ({ + tag: "publicAlbumsFooter", + height: FOOTER_HEIGHT, + item: ( + + + + ), + b: ( + + ), + }} + /> + + + ), + }); - const getAlbumsFooter = () => { - return { - itemType: ITEM_TYPE.MARKETING_FOOTER, - height: publicCollectionGalleryContext.referralCode - ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL - : ALBUM_FOOTER_HEIGHT, - item: ( - - {/* Make the entire area tappable, otherwise it is hard to - get at on mobile devices. */} - - - + const getAlbumsFooter = (): TimeStampListItem => ({ + tag: "publicAlbumsFooter", + height: publicCollectionGalleryContext.referralCode + ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL + : ALBUM_FOOTER_HEIGHT, + item: ( + + {/* Make the entire area tappable, otherwise it is hard to + get at on mobile devices. */} + + + + + ), + }} + values={{ url: "ente.io" }} + /> + + + {publicCollectionGalleryContext.referralCode ? ( + + - ), + i18nKey={"sharing_referral_code"} + values={{ + referralCode: + publicCollectionGalleryContext.referralCode, }} - values={{ url: "ente.io" }} /> - - {publicCollectionGalleryContext.referralCode ? ( - - - - - - ) : null} - - - ), - }; - }; + + ) : null} + + + ), + }); /** * Checks and merge multiple dates into a single row. @@ -546,7 +534,7 @@ export const FileList: React.FC = ({ const currItem = items[index]; // If the current item is of type time, then it is not part of an ongoing date. // So, there is a possibility of merge. - if (currItem.itemType === ITEM_TYPE.TIME) { + if (currItem.tag == "date") { // If new list pointer is not at the end of list then // we can add more items to the same list. if (newList[newIndex]) { @@ -601,7 +589,7 @@ export const FileList: React.FC = ({ for (let i = 0; i < newList.length; i++) { const currItem = newList[i]; const nextItem = newList[i + 1]; - if (currItem.itemType === ITEM_TYPE.TIME) { + if (currItem.tag == "date") { if (currItem.dates.length > 1) { currItem.groups = currItem.dates.map((item) => item.span); nextItem.groups = currItem.groups; @@ -612,10 +600,10 @@ export const FileList: React.FC = ({ }; const getItemSize = (timeStampList) => (index) => { - switch (timeStampList[index].itemType) { - case ITEM_TYPE.TIME: + switch (timeStampList[index].tag) { + case "date": return DATE_CONTAINER_HEIGHT; - case ITEM_TYPE.FILE: + case "file": return listItemHeight; default: return timeStampList[index].height; @@ -623,8 +611,8 @@ export const FileList: React.FC = ({ }; const generateKey = (index) => { - switch (timeStampList[index].itemType) { - case ITEM_TYPE.FILE: + switch (timeStampList[index].tag) { + case "file": return `${timeStampList[index].items[0].file.id}-${ timeStampList[index].items.slice(-1)[0].file.id }`; @@ -798,8 +786,8 @@ export const FileList: React.FC = ({ // Enhancement: This logic doesn't work on the shared album screen, the // galleryContext.selectedFile is always null there. const haveSelection = (galleryContext.selectedFile?.count ?? 0) > 0; - switch (listItem.itemType) { - case ITEM_TYPE.TIME: + switch (listItem.tag) { + case "date": return listItem.dates ? ( listItem.dates .map((item) => [ @@ -842,7 +830,7 @@ export const FileList: React.FC = ({ {listItem.date} ); - case ITEM_TYPE.FILE: { + case "file": { const ret = listItem.items.map((item, idx) => getThumbnail( item, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index d4a206c614..b9ead88ba2 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -11,10 +11,6 @@ import { styled } from "@mui/material"; import { t } from "i18next"; import { useCallback, useMemo, useState } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; -import { - addToFavorites, - removeFromFavorites, -} from "services/collectionService"; import uploadManager from "services/upload/uploadManager"; import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; import { downloadSingleFile } from "utils/file"; @@ -30,19 +26,6 @@ export type FileListWithViewerProps = { */ files: EnteFile[]; enableDownload?: boolean; - /** - * Called when the component wants to update the in-memory, unsynced, - * favorite status of a file. - * - * For more details, see {@link unsyncedFavoriteUpdates} in the gallery - * reducer's documentation. - * - * Not set in the context of the shared albums app. - */ - onMarkUnsyncedFavoriteUpdate?: ( - fileID: number, - isFavorite: boolean, - ) => void; /** * Called when the component wants to mark the given files as deleted in the * the in-memory, unsynced, state maintained by the top level gallery. @@ -79,10 +62,12 @@ export type FileListWithViewerProps = { | "user" | "isInIncomingSharedCollection" | "isInHiddenSection" - | "fileCollectionIDs" - | "allCollectionsNameByID" + | "fileNormalCollectionIDs" + | "collectionNameByID" + | "pendingFavoriteUpdates" | "pendingVisibilityUpdates" | "onVisualFeedback" + | "onToggleFavorite" | "onFileVisibilityUpdate" | "onSelectCollection" | "onSelectPerson" @@ -108,16 +93,17 @@ export const FileListWithViewer: React.FC = ({ favoriteFileIDs, isInIncomingSharedCollection, isInHiddenSection, - fileCollectionIDs, - allCollectionsNameByID, + fileNormalCollectionIDs, + collectionNameByID, + pendingFavoriteUpdates, pendingVisibilityUpdates, setFilesDownloadProgressAttributesCreator, - onFileVisibilityUpdate, - onMarkUnsyncedFavoriteUpdate, - onMarkTempDeleted, onSetOpenFileViewer, onSyncWithRemote, onVisualFeedback, + onToggleFavorite, + onFileVisibilityUpdate, + onMarkTempDeleted, onSelectCollection, onSelectPerson, }) => { @@ -149,20 +135,6 @@ export const FileListWithViewer: React.FC = ({ [onSyncWithRemote], ); - const handleToggleFavorite = useMemo(() => { - return favoriteFileIDs && onMarkUnsyncedFavoriteUpdate - ? async (file: EnteFile) => { - const isFavorite = favoriteFileIDs!.has(file.id); - await (isFavorite ? removeFromFavorites : addToFavorites)( - file, - true, - ); - // See: [Note: File viewer update and dispatch] - onMarkUnsyncedFavoriteUpdate(file.id, !isFavorite); - } - : undefined; - }, [favoriteFileIDs, onMarkUnsyncedFavoriteUpdate]); - const handleDownload = useCallback( (file: EnteFile) => { const setSingleFileDownloadProgress = @@ -221,16 +193,17 @@ export const FileListWithViewer: React.FC = ({ isInHiddenSection, isInIncomingSharedCollection, favoriteFileIDs, - fileCollectionIDs, - allCollectionsNameByID, + fileNormalCollectionIDs, + collectionNameByID, + pendingFavoriteUpdates, pendingVisibilityUpdates, onVisualFeedback, + onToggleFavorite, onFileVisibilityUpdate, onSelectCollection, onSelectPerson, }} onTriggerSyncWithRemote={handleTriggerSyncWithRemote} - onToggleFavorite={handleToggleFavorite} onDownload={handleDownload} onDelete={handleDelete} onSaveEditedImageCopy={handleSaveEditedImageCopy} diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index 94bc2e46e3..fa8b2232e3 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -1,6 +1,6 @@ import { FilledIconButton } from "@/base/components/mui"; import { useBaseContext } from "@/base/context"; -import { UPLOAD_RESULT, type UploadPhase } from "@/gallery/services/upload"; +import { type UploadPhase, type UploadResult } from "@/gallery/services/upload"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import CloseIcon from "@mui/icons-material/Close"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; @@ -265,13 +265,12 @@ function UploadProgressDialog() { useEffect(() => { if ( - finishedUploads.get(UPLOAD_RESULT.ALREADY_UPLOADED)?.length > 0 || - finishedUploads.get(UPLOAD_RESULT.BLOCKED)?.length > 0 || - finishedUploads.get(UPLOAD_RESULT.FAILED)?.length > 0 || - finishedUploads.get(UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE) - ?.length > 0 || - finishedUploads.get(UPLOAD_RESULT.TOO_LARGE)?.length > 0 || - finishedUploads.get(UPLOAD_RESULT.UNSUPPORTED)?.length > 0 + finishedUploads.get("alreadyUploaded")?.length > 0 || + finishedUploads.get("blocked")?.length > 0 || + finishedUploads.get("failed")?.length > 0 || + finishedUploads.get("largerThanAvailableStorage")?.length > 0 || + finishedUploads.get("tooLarge")?.length > 0 || + finishedUploads.get("unsupported")?.length > 0 ) { setHasUnUploadedFiles(true); } else { @@ -290,13 +289,11 @@ function UploadProgressDialog() { {uploadPhase === "uploading" && } @@ -306,12 +303,12 @@ function UploadProgressDialog() { )} } /> @@ -457,7 +452,7 @@ const NotUploadSectionHeader = styled("div")( ); interface ResultSectionProps { - uploadResult: UPLOAD_RESULT; + uploadResult: UploadResult; sectionTitle: string; sectionInfo?: React.ReactNode; } @@ -552,8 +547,8 @@ const DoneFooter: React.FC = () => { return ( {uploadPhase == "done" && - (finishedUploads?.get(UPLOAD_RESULT.FAILED)?.length > 0 || - finishedUploads?.get(UPLOAD_RESULT.BLOCKED)?.length > 0 ? ( + (finishedUploads?.get("failed")?.length > 0 || + finishedUploads?.get("blocked")?.length > 0 ? ( diff --git a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx index e9bc01a1fc..6e75c9e93c 100644 --- a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx +++ b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx @@ -23,15 +23,13 @@ import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { IconButton, Tooltip, Typography } from "@mui/material"; import { t } from "i18next"; -import { COLLECTION_OPS_TYPE } from "utils/collection"; -import { FILE_OPS_TYPE } from "utils/file"; +import { type CollectionOp } from "utils/collection"; +import { type FileOp } from "utils/file"; interface Props { - handleCollectionOps: ( - opsType: COLLECTION_OPS_TYPE, - ) => (...args: any[]) => void; - handleFileOps: (opsType: FILE_OPS_TYPE) => (...args: any[]) => void; - showCreateCollectionModal: (opsType: COLLECTION_OPS_TYPE) => () => void; + handleCollectionOp: (op: CollectionOp) => (...args: any[]) => void; + handleFileOp: (op: FileOp) => (...args: any[]) => void; + showCreateCollectionModal: (op: CollectionOp) => () => void; /** * Callback to open a dialog where the user can choose a collection. * @@ -78,8 +76,8 @@ interface Props { const SelectedFileOptions = ({ showCreateCollectionModal, onOpenCollectionSelector, - handleCollectionOps, - handleFileOps, + handleCollectionOp, + handleFileOp, selectedCollection, count, ownCount, @@ -99,10 +97,8 @@ const SelectedFileOptions = ({ const addToCollection = () => onOpenCollectionSelector({ action: "add", - onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.ADD), - onCreateCollection: showCreateCollectionModal( - COLLECTION_OPS_TYPE.ADD, - ), + onSelectCollection: handleCollectionOp("add"), + onCreateCollection: showCreateCollectionModal("add"), relatedCollectionID: isInSearchMode || peopleMode ? undefined : activeCollectionID, }); @@ -114,7 +110,7 @@ const SelectedFileOptions = ({ continue: { text: t("move_to_trash"), color: "critical", - action: handleFileOps(FILE_OPS_TYPE.TRASH), + action: handleFileOp("trash"), }, }); @@ -125,19 +121,15 @@ const SelectedFileOptions = ({ continue: { text: t("delete"), color: "critical", - action: handleFileOps(FILE_OPS_TYPE.DELETE_PERMANENTLY), + action: handleFileOp("deletePermanently"), }, }); const restoreHandler = () => onOpenCollectionSelector({ action: "restore", - onSelectCollection: handleCollectionOps( - COLLECTION_OPS_TYPE.RESTORE, - ), - onCreateCollection: showCreateCollectionModal( - COLLECTION_OPS_TYPE.RESTORE, - ), + onSelectCollection: handleCollectionOp("restore"), + onCreateCollection: showCreateCollectionModal("restore"), }); const removeFromCollectionHandler = () => { @@ -150,9 +142,7 @@ const SelectedFileOptions = ({ color: "primary", action: () => - handleCollectionOps(COLLECTION_OPS_TYPE.REMOVE)( - selectedCollection, - ), + handleCollectionOp("remove")(selectedCollection), }, }); } else { @@ -163,9 +153,7 @@ const SelectedFileOptions = ({ text: t("yes_remove"), color: "critical", action: () => - handleCollectionOps(COLLECTION_OPS_TYPE.REMOVE)( - selectedCollection, - ), + handleCollectionOp("remove")(selectedCollection), }, }); } @@ -174,10 +162,8 @@ const SelectedFileOptions = ({ const moveToCollection = () => { onOpenCollectionSelector({ action: "move", - onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.MOVE), - onCreateCollection: showCreateCollectionModal( - COLLECTION_OPS_TYPE.MOVE, - ), + onSelectCollection: handleCollectionOp("move"), + onCreateCollection: showCreateCollectionModal("move"), relatedCollectionID: isInSearchMode || peopleMode ? undefined : activeCollectionID, }); @@ -186,10 +172,8 @@ const SelectedFileOptions = ({ const unhideToCollection = () => { onOpenCollectionSelector({ action: "unhide", - onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.UNHIDE), - onCreateCollection: showCreateCollectionModal( - COLLECTION_OPS_TYPE.UNHIDE, - ), + onSelectCollection: handleCollectionOp("unhide"), + onCreateCollection: showCreateCollectionModal("unhide"), }); }; @@ -210,16 +194,12 @@ const SelectedFileOptions = ({ {isInSearchMode ? ( <> - + - + @@ -229,14 +209,12 @@ const SelectedFileOptions = ({ - + - + @@ -249,9 +227,7 @@ const SelectedFileOptions = ({ ) : peopleMode ? ( <> - + @@ -261,14 +237,12 @@ const SelectedFileOptions = ({ - + - + @@ -294,9 +268,7 @@ const SelectedFileOptions = ({ ) : isUncategorizedCollection ? ( <> - + @@ -313,7 +285,7 @@ const SelectedFileOptions = ({ ) : isIncomingSharedCollection ? ( - + @@ -325,9 +297,7 @@ const SelectedFileOptions = ({ - + @@ -341,28 +311,20 @@ const SelectedFileOptions = ({ ) : ( <> - + {!isFavoriteCollection && activeCollectionID != ARCHIVE_SECTION && ( - + )} - + @@ -373,18 +335,14 @@ const SelectedFileOptions = ({ {activeCollectionID === ARCHIVE_SECTION && ( - + )} {activeCollectionID === ALL_SECTION && ( - + @@ -409,7 +367,7 @@ const SelectedFileOptions = ({ )} - + diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index e5fb376ae5..79ca24fcd0 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -32,7 +32,6 @@ import HTTPService from "@ente/shared/network/HTTPService"; import { getData, isLocalStorageAndIndexedDBMismatch, - LS_KEYS, } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import "@fontsource-variable/inter"; @@ -66,7 +65,7 @@ const App: React.FC = ({ Component, pageProps }) => { const logout = useCallback(() => void photosLogout(), []); useEffect(() => { - const user = getData(LS_KEYS.USER) as User | undefined | null; + const user = getData("user") as User | undefined | null; logStartupBanner(user?.id); HTTPService.setHeaders({ "X-Client-Package": clientPackageName }); void isLocalStorageAndIndexedDBMismatch().then((mismatch) => { @@ -126,11 +125,11 @@ const App: React.FC = ({ Component, pageProps }) => { useEffect(() => { const query = new URLSearchParams(window.location.search); const needsFamilyRedirect = query.get("redirect") == "families"; - if (needsFamilyRedirect && getData(LS_KEYS.USER)?.token) + if (needsFamilyRedirect && getData("user")?.token) redirectToFamilyPortal(); router.events.on("routeChangeStart", () => { - if (needsFamilyRedirect && getData(LS_KEYS.USER)?.token) { + if (needsFamilyRedirect && getData("user")?.token) { redirectToFamilyPortal(); // https://github.com/vercel/next.js/issues/2476#issuecomment-573460710 diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 66a8f654fd..948644c669 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -68,10 +68,9 @@ import { } from "@/new/photos/services/user-details"; import { usePhotosAppContext } from "@/new/photos/types/context"; import { FlexWrapper } from "@ente/shared/components/Container"; -import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { getRecoveryKey } from "@ente/shared/crypto/helpers"; import { CustomError } from "@ente/shared/error"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { getToken, isFirstLogin, @@ -79,11 +78,7 @@ import { setIsFirstLogin, setJustSignedUp, } from "@ente/shared/storage/localStorage/helpers"; -import { - SESSION_KEYS, - clearKeys, - getKey, -} from "@ente/shared/storage/sessionStorage"; +import { clearKeys, getKey } from "@ente/shared/storage/sessionStorage"; import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; @@ -94,7 +89,7 @@ import CollectionNamer, { } from "components/Collections/CollectionNamer"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { Export } from "components/Export"; -import { ITEM_TYPE, TimeStampListItem } from "components/FileList"; +import { TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FilesDownloadProgress, @@ -111,10 +106,12 @@ import { createContext, useCallback, useEffect, useRef, useState } from "react"; import { FileWithPath } from "react-dropzone"; import { Trans } from "react-i18next"; import { + addToFavorites, constructEmailList, constructUserIDToEmailMap, createAlbum, createUnCategorizedCollection, + removeFromFavorites, } from "services/collectionService"; import exportService from "services/export"; import uploadManager from "services/upload/uploadManager"; @@ -126,11 +123,11 @@ import { SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; import { - COLLECTION_OPS_TYPE, getSelectedCollection, - handleCollectionOps, + handleCollectionOp, + type CollectionOp, } from "utils/collection"; -import { FILE_OPS_TYPE, getSelectedFiles, handleFileOps } from "utils/file"; +import { getSelectedFiles, handleFileOp, type FileOp } from "utils/file"; const defaultGalleryContext: GalleryContextType = { setActiveCollectionID: () => null, @@ -254,13 +251,24 @@ const Page: React.FC = () => { [], ); - // TODO: Temp - const user = state.user; - const familyData = state.familyData; - const collections = state.collections; - const files = state.files; - const hiddenFiles = state.hiddenFiles; - const collectionSummaries = state.collectionSummaries; + // Local aliases. + const { + user, + familyData, + normalCollections, + normalFiles, + hiddenFiles, + favoriteFileIDs, + collectionNameByID, + fileNormalCollectionIDs, + normalCollectionSummaries, + pendingFavoriteUpdates, + pendingVisibilityUpdates, + isInSearchMode, + filteredFiles, + } = state; + + // Derived aliases. const barMode = state.view?.type ?? "albums"; const activeCollectionID = state.view?.type == "people" @@ -271,8 +279,6 @@ const Page: React.FC = () => { const activePerson = state.view?.type == "people" ? state.view.activePerson : undefined; const activePersonID = activePerson?.id; - const isInSearchMode = state.isInSearchMode; - const filteredFiles = state.filteredFiles; if (process.env.NEXT_PUBLIC_ENTE_TRACE) console.log("render", state); @@ -292,10 +298,10 @@ const Page: React.FC = () => { }; useEffect(() => { - const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); + const key = getKey("encryptionKey"); const token = getToken(); if (!key || !token) { - stashRedirect(PAGES.GALLERY); + stashRedirect("/gallery"); router.push("/"); return; } @@ -315,15 +321,15 @@ const Page: React.FC = () => { showPlanSelector(); } setIsFirstLogin(false); - const user = getData(LS_KEYS.USER); + const user = getData("user"); // TODO: Pass entire snapshot to reducer? const familyData = userDetailsSnapshot()?.familyData; dispatch({ type: "mount", user, familyData, - allCollections: await getAllLocalCollections(), - files: await getLocalFiles("normal"), + collections: await getAllLocalCollections(), + normalFiles: await getLocalFiles("normal"), hiddenFiles: await getLocalFiles("hidden"), trashedFiles: await getLocalTrashedFiles(), }); @@ -346,26 +352,35 @@ const Page: React.FC = () => { }; }, []); - useEffect( - () => setSearchCollectionsAndFiles({ collections, files }), - [collections, files], - ); + useEffect(() => { + setSearchCollectionsAndFiles({ + collections: normalCollections, + files: normalFiles, + }); + }, [normalCollections, normalFiles]); useEffect(() => { - if (!collections || !user) { + if (!user || !normalCollections) { return; } - const userIdToEmailMap = constructUserIDToEmailMap(user, collections); + const userIdToEmailMap = constructUserIDToEmailMap( + user, + normalCollections, + ); setUserIDToEmailMap(userIdToEmailMap); - }, [collections]); + }, [user, normalCollections]); useEffect(() => { - if (!user || !collections) { + if (!user || !normalCollections) { return; } - const emailList = constructEmailList(user, collections, familyData); + const emailList = constructEmailList( + user, + normalCollections, + familyData, + ); setEmailList(emailList); - }, [user, collections, familyData]); + }, [user, normalCollections, familyData]); useEffect(() => { collectionNamerAttributes && setCollectionNamerView(true); @@ -385,7 +400,7 @@ const Page: React.FC = () => { }, [activeCollectionID, router.isReady]); useEffect(() => { - if (router.isReady && getKey(SESSION_KEYS.ENCRYPTION_KEY)) { + if (router.isReady && getKey("encryptionKey")) { handleSubscriptionCompletionRedirectIfNeeded( showMiniDialog, showLoadingBar, @@ -408,7 +423,7 @@ const Page: React.FC = () => { fileCount={state.searchResults?.length ?? 0} /> ), - itemType: ITEM_TYPE.HEADER, + tag: "header", }); } }, [isInSearchMode, state.searchSuggestion, state.searchResults]); @@ -520,22 +535,27 @@ const Page: React.FC = () => { const handleFileAndCollectionSyncWithRemote = useCallback(async () => { const didUpdateFiles = await syncCollectionAndFiles({ - onSetCollections: (normalCollections, hiddenCollections) => + onSetCollections: ( + collections, + normalCollections, + hiddenCollections, + ) => dispatch({ type: "setCollections", + collections, normalCollections, hiddenCollections, }), onResetNormalFiles: (files) => - dispatch({ type: "setFiles", files }), + dispatch({ type: "setNormalFiles", files }), onFetchNormalFiles: (files) => - dispatch({ type: "fetchFiles", files }), - onResetHiddenFiles: (hiddenFiles) => - dispatch({ type: "setHiddenFiles", hiddenFiles }), - onFetchHiddenFiles: (hiddenFiles) => - dispatch({ type: "fetchHiddenFiles", hiddenFiles }), - onResetTrashedFiles: (trashedFiles) => - dispatch({ type: "setTrashedFiles", trashedFiles }), + dispatch({ type: "fetchNormalFiles", files }), + onResetHiddenFiles: (files) => + dispatch({ type: "setHiddenFiles", files }), + onFetchHiddenFiles: (files) => + dispatch({ type: "fetchHiddenFiles", files }), + onResetTrashedFiles: (files) => + dispatch({ type: "setTrashedFiles", files }), }); if (didUpdateFiles) { exportService.onLocalFilesUpdated(); @@ -578,7 +598,7 @@ const Page: React.FC = () => { break; case CustomError.KEY_MISSING: clearKeys(); - router.push(PAGES.CREDENTIALS); + router.push("/credentials"); break; default: log.error("syncWithRemote failed", e); @@ -659,20 +679,20 @@ const Page: React.FC = () => { }, []); const collectionOpsHelper = - (ops: COLLECTION_OPS_TYPE) => async (collection: Collection) => { + (op: CollectionOp) => async (collection: Collection) => { showLoadingBar(); try { setOpenCollectionSelector(false); const selectedFiles = getSelectedFiles(selected, filteredFiles); const toProcessFiles = - ops === COLLECTION_OPS_TYPE.REMOVE + op == "remove" ? selectedFiles : selectedFiles.filter( (file) => file.ownerID === user.id, ); if (toProcessFiles.length > 0) { - await handleCollectionOps( - ops, + await handleCollectionOp( + op, collection, toProcessFiles, selected.collectionID, @@ -687,21 +707,21 @@ const Page: React.FC = () => { } }; - const fileOpsHelper = (ops: FILE_OPS_TYPE) => async () => { + const fileOpHelper = (op: FileOp) => async () => { showLoadingBar(); try { // passing files here instead of filteredData for hide ops because we want to move all files copies to hidden collection const selectedFiles = getSelectedFiles( selected, - ops === FILE_OPS_TYPE.HIDE ? files : filteredFiles, + op == "hide" ? normalFiles : filteredFiles, ); const toProcessFiles = - ops === FILE_OPS_TYPE.DOWNLOAD + op == "download" ? selectedFiles : selectedFiles.filter((file) => file.ownerID === user.id); if (toProcessFiles.length > 0) { - await handleFileOps( - ops, + await handleFileOp( + op, toProcessFiles, handleMarkTempDeleted, () => dispatch({ type: "clearTempDeleted" }), @@ -723,12 +743,12 @@ const Page: React.FC = () => { } }; - const showCreateCollectionModal = (ops: COLLECTION_OPS_TYPE) => { + const showCreateCollectionModal = (op: CollectionOp) => { const callback = async (collectionName: string) => { try { showLoadingBar(); const collection = await createAlbum(collectionName); - await collectionOpsHelper(ops)(collection); + await collectionOpsHelper(op)(collection); } catch (e) { onGenericError(e); } finally { @@ -751,7 +771,7 @@ const Page: React.FC = () => { if (type == "collection" || type == "person") { if (type == "collection") { dispatch({ - type: "showNormalOrHiddenCollectionSummary", + type: "showCollectionSummary", collectionSummaryID: searchOption.suggestion.collectionID, }); } else { @@ -781,11 +801,7 @@ const Page: React.FC = () => { const handleSetActiveCollectionID = ( collectionSummaryID: number | undefined, - ) => - dispatch({ - type: "showNormalOrHiddenCollectionSummary", - collectionSummaryID, - }); + ) => dispatch({ type: "showCollectionSummary", collectionSummaryID }); const handleChangeBarMode = (mode: GalleryBarMode) => mode == "people" @@ -801,6 +817,29 @@ const Page: React.FC = () => { }); }; + const handleToggleFavorite = useCallback( + async (file: EnteFile) => { + const fileID = file.id; + const isFavorite = favoriteFileIDs.has(fileID); + + dispatch({ type: "addPendingFavoriteUpdate", fileID }); + try { + await (isFavorite ? removeFromFavorites : addToFavorites)( + file, + true, + ); + dispatch({ + type: "unsyncedFavoriteUpdate", + fileID, + isFavorite: !isFavorite, + }); + } finally { + dispatch({ type: "removePendingFavoriteUpdate", fileID }); + } + }, + [favoriteFileIDs], + ); + const handleFileViewerFileVisibilityUpdate = useCallback( async (file: EnteFile, visibility: ItemVisibility) => { const fileID = file.id; @@ -822,16 +861,6 @@ const Page: React.FC = () => { [], ); - const handleMarkUnsyncedFavoriteUpdate = useCallback( - (fileID: number, isFavorite: boolean) => - dispatch({ - type: "markUnsyncedFavoriteUpdate", - fileID, - isFavorite, - }), - [], - ); - const handleMarkTempDeleted = useCallback( (files: EnteFile[]) => dispatch({ type: "markTempDeleted", files }), [], @@ -840,7 +869,7 @@ const Page: React.FC = () => { const handleSelectCollection = useCallback( (collectionID: number) => dispatch({ - type: "showNormalOrHiddenCollectionSummary", + type: "showCollectionSummary", collectionSummaryID: collectionID, }), [], @@ -914,10 +943,10 @@ const Page: React.FC = () => { open={openCollectionSelector} onClose={handleCloseCollectionSelector} attributes={collectionSelectorAttributes} - collectionSummaries={collectionSummaries} + collectionSummaries={normalCollectionSummaries} collectionForCollectionID={(id) => findCollectionCreatingUncategorizedIfNeeded( - collections, + normalCollections, id, ) } @@ -942,8 +971,8 @@ const Page: React.FC = () => { > {showSelectionBar ? ( { activeCollectionID={activeCollectionID} selectedCollection={getSelectedCollection( selected.collectionID, - collections, + normalCollections, )} isFavoriteCollection={ - collectionSummaries.get(activeCollectionID) - ?.type == "favorites" + normalCollectionSummaries.get( + activeCollectionID, + )?.type == "favorites" } isUncategorizedCollection={ - collectionSummaries.get(activeCollectionID) - ?.type == "uncategorized" + normalCollectionSummaries.get( + activeCollectionID, + )?.type == "uncategorized" } isIncomingSharedCollection={ - collectionSummaries.get(activeCollectionID) - ?.type == "incomingShareCollaborator" || - collectionSummaries.get(activeCollectionID) - ?.type == "incomingShareViewer" + normalCollectionSummaries.get( + activeCollectionID, + )?.type == "incomingShareCollaborator" || + normalCollectionSummaries.get( + activeCollectionID, + )?.type == "incomingShareViewer" } isInSearchMode={isInSearchMode} isInHiddenSection={barMode == "hidden-albums"} @@ -1001,26 +1034,26 @@ const Page: React.FC = () => { { setCollectionNamerAttributes={setCollectionNamerAttributes} setShouldDisableDropzone={setShouldDisableDropzone} onUploadFile={(file) => - dispatch({ type: "uploadFile", file }) + dispatch({ type: "uploadNormalFile", file }) } onShowPlanSelector={showPlanSelector} setCollections={(collections) => dispatch({ type: "setNormalCollections", collections }) } isFirstUpload={areOnlySystemCollections( - collectionSummaries, + normalCollectionSummaries, )} showSessionExpiredMessage={showSessionExpiredDialog} {...{ @@ -1054,7 +1087,7 @@ const Page: React.FC = () => { /> { {!isInSearchMode && !isFirstLoad && - !files?.length && + !normalFiles?.length && !hiddenFiles?.length && activeCollectionID === ALL_SECTION ? ( @@ -1079,35 +1112,34 @@ const Page: React.FC = () => { files={filteredFiles} enableDownload={true} showAppDownloadBanner={ - files.length < 30 && !isInSearchMode + normalFiles.length < 30 && !isInSearchMode } selectable={true} selected={selected} setSelected={setSelected} activeCollectionID={activeCollectionID} activePersonID={activePerson?.id} - fileCollectionIDs={state.fileCollectionIDs} - allCollectionsNameByID={state.allCollectionsNameByID} isInIncomingSharedCollection={ - collectionSummaries.get(activeCollectionID)?.type == - "incomingShareCollaborator" || - collectionSummaries.get(activeCollectionID)?.type == - "incomingShareViewer" + normalCollectionSummaries.get(activeCollectionID) + ?.type == "incomingShareCollaborator" || + normalCollectionSummaries.get(activeCollectionID) + ?.type == "incomingShareViewer" } isInHiddenSection={barMode == "hidden-albums"} - pendingVisibilityUpdates={ - state.pendingVisibilityUpdates - } - favoriteFileIDs={state.favoriteFileIDs} + {...{ + favoriteFileIDs, + collectionNameByID, + fileNormalCollectionIDs, + pendingFavoriteUpdates, + pendingVisibilityUpdates, + }} setFilesDownloadProgressAttributesCreator={ setFilesDownloadProgressAttributesCreator } + onToggleFavorite={handleToggleFavorite} onFileVisibilityUpdate={ handleFileViewerFileVisibilityUpdate } - onMarkUnsyncedFavoriteUpdate={ - handleMarkUnsyncedFavoriteUpdate - } onMarkTempDeleted={handleMarkTempDeleted} onSetOpenFileViewer={setIsFileViewerOpen} onSyncWithRemote={handleSyncWithRemote} @@ -1118,7 +1150,7 @@ const Page: React.FC = () => { )} { currentURL.pathname = router.pathname; if ( currentURL.host === albumsURL.host && - currentURL.pathname !== PAGES.SHARED_ALBUMS + currentURL.pathname != "/shared-albums" ) { handleAlbumsRedirect(currentURL); } else { @@ -53,7 +52,7 @@ const Page: React.FC = () => { const end = currentURL.hash.lastIndexOf("&"); const hash = currentURL.hash.slice(1, end !== -1 ? end : undefined); await router.replace({ - pathname: PAGES.SHARED_ALBUMS, + pathname: "/shared-albums", search: currentURL.search, hash: hash, }); @@ -61,8 +60,8 @@ const Page: React.FC = () => { }; const handleNormalRedirect = async () => { - const user = getData(LS_KEYS.USER); - let key = getKey(SESSION_KEYS.ENCRYPTION_KEY); + const user = getData("user"); + let key = getKey("encryptionKey"); const electron = globalThis.electron; if (!key && electron) { try { @@ -71,18 +70,14 @@ const Page: React.FC = () => { log.error("Failed to read master key from safe storage", e); } if (key) { - await saveKeyInSessionStore( - SESSION_KEYS.ENCRYPTION_KEY, - key, - true, - ); + await saveKeyInSessionStore("encryptionKey", key, true); } } const token = getToken(); if (key && token) { - await router.push(PAGES.GALLERY); + await router.push("/gallery"); } else if (user?.email) { - await router.push(PAGES.VERIFY); + await router.push("/verify"); } await initLocalForage(); setLoading(false); @@ -107,8 +102,8 @@ const Page: React.FC = () => { const signUp = () => setShowLogin(false); const login = () => setShowLogin(true); - const redirectToSignupPage = () => router.push(PAGES.SIGNUP); - const redirectToLoginPage = () => router.push(PAGES.LOGIN); + const redirectToSignupPage = () => router.push("/signup"); + const redirectToLoginPage = () => router.push("/login"); return ( diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 5a5873f210..143394c1d9 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -43,7 +43,6 @@ import { CenteredFlex } from "@ente/shared/components/Container"; import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; -import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; import CloseIcon from "@mui/icons-material/Close"; @@ -51,7 +50,7 @@ import DownloadIcon from "@mui/icons-material/Download"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; import Typography from "@mui/material/Typography"; -import { ITEM_TYPE, TimeStampListItem } from "components/FileList"; +import { TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FilesDownloadProgress, @@ -185,7 +184,7 @@ export default function PublicCollectionGallery() { if (currentURL.pathname !== "/") { router.replace( { - pathname: PAGES.SHARED_ALBUMS, + pathname: "/shared-albums", search: currentURL.search, hash: currentURL.hash, }, @@ -266,7 +265,7 @@ export default function PublicCollectionGallery() { }} /> ), - itemType: ITEM_TYPE.HEADER, + tag: "header", height: 68, }); }, [publicCollection, publicFiles]); @@ -280,7 +279,6 @@ export default function PublicCollectionGallery() { ), - itemType: ITEM_TYPE.FOOTER, height: 104, } : null, @@ -522,8 +520,6 @@ export default function PublicCollectionGallery() { selected={selected} setSelected={setSelected} activeCollectionID={ALL_SECTION} - fileCollectionIDs={undefined} - allCollectionsNameByID={undefined} setFilesDownloadProgressAttributesCreator={ setFilesDownloadProgressAttributesCreator } diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index d4e4c9b7b5..f0e58f74d9 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -8,12 +8,12 @@ import { CollectionMagicMetadata, CollectionMagicMetadataProps, CollectionPublicMagicMetadata, - CollectionType, + CollectionSubType, + type CollectionType, CreatePublicAccessTokenRequest, EncryptedCollection, PublicURL, RemoveFromCollectionRequest, - SUB_TYPE, UpdatePublicURL, } from "@/media/collection"; import { EncryptedMagicMetadata, EnteFile } from "@/media/file"; @@ -40,7 +40,7 @@ import { import type { FamilyData } from "@/new/photos/services/user-details"; import { batch } from "@/utils/array"; import HTTPService from "@ente/shared/network/HTTPService"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import type { User } from "@ente/shared/user/types"; @@ -58,7 +58,7 @@ const FAVORITE_COLLECTION_NAME = "Favorites"; const REQUEST_BATCH_SIZE = 1000; export const createAlbum = (albumName: string) => { - return createCollection(albumName, CollectionType.album); + return createCollection(albumName, "album"); }; const createCollection = async ( @@ -136,7 +136,7 @@ const postCollection = async ( }; export const createFavoritesCollection = () => { - return createCollection(FAVORITE_COLLECTION_NAME, CollectionType.favorites); + return createCollection(FAVORITE_COLLECTION_NAME, "favorites"); }; export const addToFavorites = async ( @@ -190,7 +190,7 @@ export const removeFromCollection = async ( allFiles?: EnteFile[], ) => { try { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); const nonUserFiles = []; const userFiles = []; for (const file of toRemoveFiles) { @@ -232,7 +232,7 @@ export const removeUserFiles = async ( const collections = await getLocalCollections(); const collectionsMap = new Map(collections.map((c) => [c.id, c])); - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); for (const [targetCollectionID, files] of groupedFiles.entries()) { const targetCollection = collectionsMap.get(targetCollectionID); @@ -469,7 +469,7 @@ export const renameCollection = async ( ) => { if (isQuickLinkCollection(collection)) { // Convert quick link collection to normal collection on rename - await changeCollectionSubType(collection, SUB_TYPE.DEFAULT); + await changeCollectionSubType(collection, CollectionSubType.default); } const token = getToken(); const cryptoWorker = await sharedCryptoWorker(); @@ -604,7 +604,7 @@ export const updateShareableURL = async ( export const getFavCollection = async () => { const collections = await getLocalCollections(); for (const collection of collections) { - if (collection.type === CollectionType.favorites) { + if (collection.type == "favorites") { return collection; } } @@ -662,17 +662,14 @@ export async function getUncategorizedCollection( collections = await getLocalCollections(); } const uncategorizedCollection = collections.find( - (collection) => collection.type === CollectionType.uncategorized, + (collection) => collection.type == "uncategorized", ); return uncategorizedCollection; } export function createUnCategorizedCollection() { - return createCollection( - UNCATEGORIZED_COLLECTION_NAME, - CollectionType.uncategorized, - ); + return createCollection(UNCATEGORIZED_COLLECTION_NAME, "uncategorized"); } export async function getDefaultHiddenCollection(): Promise { @@ -685,8 +682,8 @@ export async function getDefaultHiddenCollection(): Promise { } export function createHiddenCollection() { - return createCollection(HIDDEN_COLLECTION_NAME, CollectionType.album, { - subType: SUB_TYPE.DEFAULT_HIDDEN, + return createCollection(HIDDEN_COLLECTION_NAME, "album", { + subType: CollectionSubType.defaultHidden, visibility: ItemVisibility.hidden, }); } diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index 1b6afd4512..525cb74211 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -21,7 +21,7 @@ import { getAllLocalFiles } from "@/new/photos/services/files"; import { safeDirectoryName, safeFileName } from "@/new/photos/utils/native-fs"; import { PromiseQueue } from "@/utils/promise"; import { CustomError } from "@ente/shared/error"; -import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; +import { getData, setData } from "@ente/shared/storage/localStorage"; import i18n from "i18next"; import { migrateExport, type ExportRecord } from "./migration"; @@ -34,16 +34,18 @@ const exportRecordFileName = "export_status.json"; */ const exportDirectoryName = "Ente Photos"; -export enum ExportStage { - INIT = 0, - MIGRATION = 1, - STARTING = 2, - EXPORTING_FILES = 3, - TRASHING_DELETED_FILES = 4, - RENAMING_COLLECTION_FOLDERS = 5, - TRASHING_DELETED_COLLECTIONS = 6, - FINISHED = 7, -} +export const ExportStage = { + init: 0, + migration: 1, + starting: 2, + exportingFiles: 3, + trashingDeletedFiles: 4, + renamingCollectionFolders: 5, + trashingDeletedCollections: 6, + finished: 7, +} as const; + +export type ExportStage = (typeof ExportStage)[keyof typeof ExportStage]; export interface ExportProgress { success: number; @@ -63,7 +65,7 @@ export type FileExportNames = Record; export const NULL_EXPORT_RECORD: ExportRecord = { version: 3, lastAttemptTimestamp: null, - stage: ExportStage.INIT, + stage: ExportStage.init, fileExportNames: {}, collectionExportNames: {}, }; @@ -120,7 +122,7 @@ class ExportService { if (this.exportSettings) { return this.exportSettings; } - const exportSettings = getData(LS_KEYS.EXPORT); + const exportSettings = getData("export"); this.exportSettings = exportSettings; return exportSettings; } catch (e) { @@ -134,7 +136,7 @@ class ExportService { const exportSettings = this.getExportSettings(); const newSettings = { ...exportSettings, ...newData }; this.exportSettings = newSettings; - setData(LS_KEYS.EXPORT, newSettings); + setData("export", newSettings); } catch (e) { log.error("updateExportSettings failed", e); throw e; @@ -239,23 +241,23 @@ class ExportService { async preExport(exportFolder: string) { await this.verifyExportFolderExists(exportFolder); const exportRecord = await this.getExportRecord(exportFolder); - await this.updateExportStage(ExportStage.MIGRATION); + await this.updateExportStage(ExportStage.migration); await this.runMigration( exportFolder, exportRecord, this.updateExportProgress.bind(this), ); - await this.updateExportStage(ExportStage.STARTING); + await this.updateExportStage(ExportStage.starting); } async postExport() { try { const exportFolder = this.getExportSettings()?.folder; if (!(await this.exportFolderExists(exportFolder))) { - this.uiUpdater.setExportStage(ExportStage.INIT); + this.uiUpdater.setExportStage(ExportStage.init); return; } - await this.updateExportStage(ExportStage.FINISHED); + await this.updateExportStage(ExportStage.finished); await this.updateLastExportTime(Date.now()); const exportRecord = await this.getExportRecord(exportFolder); @@ -401,7 +403,7 @@ class ExportService { }); }; if (renamedCollections?.length > 0) { - this.updateExportStage(ExportStage.RENAMING_COLLECTION_FOLDERS); + this.updateExportStage(ExportStage.renamingCollectionFolders); log.info(`renaming ${renamedCollections.length} collections`); await this.collectionRenamer( exportFolder, @@ -412,7 +414,7 @@ class ExportService { } if (removedFileUIDs?.length > 0) { - this.updateExportStage(ExportStage.TRASHING_DELETED_FILES); + this.updateExportStage(ExportStage.trashingDeletedFiles); log.info(`trashing ${removedFileUIDs.length} files`); await this.fileTrasher( exportFolder, @@ -422,7 +424,7 @@ class ExportService { ); } if (filesToExport?.length > 0) { - this.updateExportStage(ExportStage.EXPORTING_FILES); + this.updateExportStage(ExportStage.exportingFiles); log.info(`exporting ${filesToExport.length} files`); await this.fileExporter( filesToExport, @@ -435,9 +437,7 @@ class ExportService { ); } if (deletedExportedCollections?.length > 0) { - this.updateExportStage( - ExportStage.TRASHING_DELETED_COLLECTIONS, - ); + this.updateExportStage(ExportStage.trashingDeletedCollections); log.info( `removing ${deletedExportedCollections.length} collections`, ); @@ -1449,7 +1449,7 @@ const parseLivePhotoExportName = ( }; const isExportInProgress = (exportStage: ExportStage) => - exportStage > ExportStage.INIT && exportStage < ExportStage.FINISHED; + exportStage > ExportStage.init && exportStage < ExportStage.finished; /** * Move {@link fileName} in {@link collectionName} to the special per-collection diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index b5113ea226..b982a34f1f 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -15,7 +15,7 @@ import { sanitizeFilename, } from "@/new/photos/utils/native-fs"; import { wait } from "@/utils/promise"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { getIDBasedSortedFiles, getPersonalFiles } from "utils/file"; import { @@ -135,7 +135,7 @@ async function migrationV0ToV1( return; } const collectionIDPathMap = new Map(); - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); const localFiles = mergeMetadata(await getAllLocalFiles()); const localCollections = await getLocalCollections(); const personalFiles = getIDBasedSortedFiles( @@ -170,7 +170,7 @@ async function migrationV2ToV3( if (!exportRecord?.exportedFiles) { return; } - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); const localFiles = mergeMetadata(await getAllLocalFiles()); const personalFiles = getIDBasedSortedFiles( getPersonalFiles(localFiles, user), diff --git a/web/apps/photos/src/services/upload/upload-service.ts b/web/apps/photos/src/services/upload/upload-service.ts index 35e59e445d..3c539c6ced 100644 --- a/web/apps/photos/src/services/upload/upload-service.ts +++ b/web/apps/photos/src/services/upload/upload-service.ts @@ -14,7 +14,7 @@ import { import type { UploadItem } from "@/gallery/services/upload"; import { RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, - UPLOAD_RESULT, + type UploadResult, } from "@/gallery/services/upload"; import { detectFileTypeInfoFromChunk, @@ -528,7 +528,7 @@ type MakeProgressTracker = ( ) => unknown; interface UploadResponse { - uploadResult: UPLOAD_RESULT; + uploadResult: UploadResult; uploadedFile?: EncryptedEnteFile | EnteFile; } @@ -549,7 +549,7 @@ export const uploader = async ( abortIfCancelled: () => void, makeProgessTracker: MakeProgressTracker, ): Promise => { - log.info(`Uploading ${fileName}`); + log.info(`Upload ${fileName} | start`); try { /* * We read the file four times: @@ -574,7 +574,7 @@ export const uploader = async ( } catch (e) { if (isFileTypeNotSupportedError(e)) { log.error(`Not uploading ${fileName}`, e); - return { uploadResult: UPLOAD_RESULT.UNSUPPORTED }; + return { uploadResult: "unsupported" }; } throw e; } @@ -582,8 +582,7 @@ export const uploader = async ( const { fileTypeInfo, fileSize, lastModifiedMs } = assetDetails; const maxFileSize = 4 * 1024 * 1024 * 1024; /* 4 GB */ - if (fileSize >= maxFileSize) - return { uploadResult: UPLOAD_RESULT.TOO_LARGE }; + if (fileSize >= maxFileSize) return { uploadResult: "tooLarge" }; abortIfCancelled(); @@ -608,7 +607,7 @@ export const uploader = async ( ); if (matchInSameCollection) { return { - uploadResult: UPLOAD_RESULT.ALREADY_UPLOADED, + uploadResult: "alreadyUploaded", uploadedFile: matchInSameCollection, }; } else { @@ -616,10 +615,7 @@ export const uploader = async ( const symlink = Object.assign({}, anyMatch); symlink.collectionID = collection.id; await addToCollection(collection, [symlink]); - return { - uploadResult: UPLOAD_RESULT.ADDED_SYMLINK, - uploadedFile: symlink, - }; + return { uploadResult: "addedSymlink", uploadedFile: symlink }; } } @@ -669,8 +665,8 @@ export const uploader = async ( return { uploadResult: metadata.hasStaticThumbnail - ? UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL - : UPLOAD_RESULT.UPLOADED, + ? "uploadedWithStaticThumbnail" + : "uploaded", uploadedFile: uploadedFile, }; } catch (e) { @@ -683,13 +679,11 @@ export const uploader = async ( const error = handleUploadError(e); switch (error.message) { case CustomError.ETAG_MISSING: - return { uploadResult: UPLOAD_RESULT.BLOCKED }; + return { uploadResult: "blocked" }; case CustomError.FILE_TOO_LARGE: - return { - uploadResult: UPLOAD_RESULT.LARGER_THAN_AVAILABLE_STORAGE, - }; + return { uploadResult: "largerThanAvailableStorage" }; default: - return { uploadResult: UPLOAD_RESULT.FAILED }; + return { uploadResult: "failed" }; } } }; diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index 5674144d91..13e9ba6748 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -9,9 +9,9 @@ import { ComlinkWorker } from "@/base/worker/comlink-worker"; import type { UploadItem } from "@/gallery/services/upload"; import { RANDOM_PERCENTAGE_PROGRESS_FOR_PUT, - UPLOAD_RESULT, shouldDisableCFUploadProxy, type UploadPhase, + type UploadResult, } from "@/gallery/services/upload"; import type { Collection } from "@/media/collection"; import { @@ -64,14 +64,14 @@ export interface InProgressUpload { export interface FinishedUpload { localFileID: FileID; - result: UPLOAD_RESULT; + result: UploadResult; } export type InProgressUploads = Map; -export type FinishedUploads = Map; +export type FinishedUploads = Map; -export type SegregatedFinishedUploads = Map; +export type SegregatedFinishedUploads = Map; export interface ProgressUpdater { setPercentComplete: React.Dispatch>; @@ -161,7 +161,7 @@ class UIService { this.setTotalFileCount(count); this.filesUploadedCount = 0; this.inProgressUploads = new Map(); - this.finishedUploads = new Map(); + this.finishedUploads = new Map(); this.updateProgressBarUI(); } @@ -205,7 +205,7 @@ class UIService { this.updateProgressBarUI(); } - moveFileToResultList(key: number, uploadResult: UPLOAD_RESULT) { + moveFileToResultList(key: number, uploadResult: UploadResult) { this.finishedUploads.set(key, uploadResult); this.inProgressUploads.delete(key); this.updateProgressBarUI(); @@ -595,39 +595,36 @@ class UploadManager { private async postUploadTask( uploadableItem: UploadableUploadItem, - uploadResult: UPLOAD_RESULT, + uploadResult: UploadResult, uploadedFile: EncryptedEnteFile | EnteFile | undefined, ) { - const key = UPLOAD_RESULT[uploadResult]; - log.info( - `Uploaded ${uploadableItem.fileName} with result ${uploadResult} (${key})`, - ); + log.info(`Upload ${uploadableItem.fileName} | ${uploadResult}`); try { const electron = globalThis.electron; if (electron) await markUploaded(electron, uploadableItem); let decryptedFile: EnteFile; switch (uploadResult) { - case UPLOAD_RESULT.FAILED: - case UPLOAD_RESULT.BLOCKED: + case "failed": + case "blocked": this.failedItems.push(uploadableItem); break; - case UPLOAD_RESULT.ALREADY_UPLOADED: + case "alreadyUploaded": decryptedFile = uploadedFile as EnteFile; break; - case UPLOAD_RESULT.ADDED_SYMLINK: + case "addedSymlink": decryptedFile = uploadedFile as EnteFile; - uploadResult = UPLOAD_RESULT.UPLOADED; + uploadResult = "uploaded"; break; - case UPLOAD_RESULT.UPLOADED: - case UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL: + case "uploaded": + case "uploadedWithStaticThumbnail": decryptedFile = await decryptFile( uploadedFile as EncryptedEnteFile, uploadableItem.collection.key, ); break; - case UPLOAD_RESULT.UNSUPPORTED: - case UPLOAD_RESULT.TOO_LARGE: + case "unsupported": + case "tooLarge": // no-op break; default: @@ -635,9 +632,9 @@ class UploadManager { } if ( [ - UPLOAD_RESULT.ADDED_SYMLINK, - UPLOAD_RESULT.UPLOADED, - UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL, + "addedSymlink", + "uploaded", + "uploadedWithStaticThumbnail", ].includes(uploadResult) ) { const uploadItem = @@ -645,9 +642,8 @@ class UploadManager { uploadableItem.livePhotoAssets.image; if ( uploadItem && - (uploadResult == UPLOAD_RESULT.UPLOADED || - uploadResult == - UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL) + (uploadResult == "uploaded" || + uploadResult == "uploadedWithStaticThumbnail") ) { indexNewUpload(decryptedFile, uploadItem); } @@ -661,12 +657,12 @@ class UploadManager { return uploadResult; } catch (e) { log.error("failed to do post file upload action", e); - return UPLOAD_RESULT.FAILED; + return "failed"; } } private async watchFolderCallback( - fileUploadResult: UPLOAD_RESULT, + fileUploadResult: UploadResult, fileWithCollection: ClusteredUploadItem, uploadedFile: EncryptedEnteFile, ) { diff --git a/web/apps/photos/src/services/userService.ts b/web/apps/photos/src/services/userService.ts index 30e5f2cdb8..30f9c3ac37 100644 --- a/web/apps/photos/src/services/userService.ts +++ b/web/apps/photos/src/services/userService.ts @@ -4,7 +4,7 @@ import { apiURL } from "@/base/origins"; import type { UserDetails } from "@/new/photos/services/user-details"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { HttpStatusCode } from "axios"; @@ -36,7 +36,7 @@ export const isTokenValid = async (token: string) => { try { await putAttributes( token, - getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES), + getData("originalKeyAttributes"), ); } catch (e) { log.error("put attribute failed", e); diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 0430d0a644..f0720247e4 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -11,7 +11,7 @@ import type { FolderWatch, FolderWatchSyncedFile, } from "@/base/types/ipc"; -import { UPLOAD_RESULT } from "@/gallery/services/upload"; +import { type UploadResult } from "@/gallery/services/upload"; import type { Collection } from "@/media/collection"; import { EncryptedEnteFile } from "@/media/file"; import { @@ -324,7 +324,7 @@ class FolderWatcher { * {@link upload} gets uploaded. */ async onFileUpload( - fileUploadResult: UPLOAD_RESULT, + fileUploadResult: UploadResult, item: UploadItemWithCollection, file: EncryptedEnteFile, ) { @@ -333,10 +333,10 @@ class FolderWatcher { // file on disk). if ( [ - UPLOAD_RESULT.ADDED_SYMLINK, - UPLOAD_RESULT.UPLOADED, - UPLOAD_RESULT.UPLOADED_WITH_STATIC_THUMBNAIL, - UPLOAD_RESULT.ALREADY_UPLOADED, + "addedSymlink", + "uploaded", + "uploadedWithStaticThumbnail", + "alreadyUploaded", ].includes(fileUploadResult) ) { if (item.isLivePhoto) { @@ -354,11 +354,7 @@ class FolderWatcher { file, ); } - } else if ( - [UPLOAD_RESULT.UNSUPPORTED, UPLOAD_RESULT.TOO_LARGE].includes( - fileUploadResult, - ) - ) { + } else if (["unsupported", "tooLarge"].includes(fileUploadResult)) { if (item.isLivePhoto) { this.unUploadableFilePaths.add( ensureString(item.livePhotoAssets.image), diff --git a/web/apps/photos/src/utils/collection.ts b/web/apps/photos/src/utils/collection.ts index c60f45db68..0f6e66379b 100644 --- a/web/apps/photos/src/utils/collection.ts +++ b/web/apps/photos/src/utils/collection.ts @@ -3,12 +3,10 @@ import { joinPath } from "@/base/file-name"; import log from "@/base/log"; import { updateMagicMetadata } from "@/gallery/services/magic-metadata"; import { - COLLECTION_ROLE, type Collection, CollectionMagicMetadataProps, CollectionPublicMagicMetadataProps, - CollectionType, - SUB_TYPE, + CollectionSubType, } from "@/media/collection"; import { EnteFile } from "@/media/file"; import { ItemVisibility } from "@/media/file-metadata"; @@ -28,7 +26,7 @@ import { } from "@/new/photos/services/collections"; import { getAllLocalFiles, getLocalFiles } from "@/new/photos/services/files"; import { safeDirectoryName } from "@/new/photos/utils/native-fs"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { t } from "i18next"; import { @@ -45,41 +43,34 @@ import { } from "types/gallery"; import { downloadFilesWithProgress } from "utils/file"; -export enum COLLECTION_OPS_TYPE { - ADD, - MOVE, - REMOVE, - RESTORE, - UNHIDE, -} -export async function handleCollectionOps( - type: COLLECTION_OPS_TYPE, +export type CollectionOp = "add" | "move" | "remove" | "restore" | "unhide"; + +export async function handleCollectionOp( + op: CollectionOp, collection: Collection, selectedFiles: EnteFile[], selectedCollectionID: number, ) { - switch (type) { - case COLLECTION_OPS_TYPE.ADD: + switch (op) { + case "add": await addToCollection(collection, selectedFiles); break; - case COLLECTION_OPS_TYPE.MOVE: + case "move": await moveToCollection( selectedCollectionID, collection, selectedFiles, ); break; - case COLLECTION_OPS_TYPE.REMOVE: + case "remove": await removeFromCollection(collection.id, selectedFiles); break; - case COLLECTION_OPS_TYPE.RESTORE: + case "restore": await restoreToCollection(collection, selectedFiles); break; - case COLLECTION_OPS_TYPE.UNHIDE: + case "unhide": await unhideToCollection(collection, selectedFiles); break; - default: - throw Error("Invalid collection operation"); } } @@ -207,7 +198,7 @@ export const changeCollectionVisibility = async ( visibility, }; - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (collection.owner.id === user.id) { const updatedMagicMetadata = await updateMagicMetadata( updatedMagicMetadataProps, @@ -282,7 +273,7 @@ export const changeCollectionOrder = async ( export const changeCollectionSubType = async ( collection: Collection, - subType: SUB_TYPE, + subType: CollectionSubType, ) => { try { const updatedMagicMetadataProps: CollectionMagicMetadataProps = { @@ -302,7 +293,7 @@ export const changeCollectionSubType = async ( }; export const getUserOwnedCollections = (collections: Collection[]) => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (!user?.id) { throw Error("user missing"); } @@ -310,11 +301,11 @@ export const getUserOwnedCollections = (collections: Collection[]) => { }; export const isQuickLinkCollection = (collection: Collection) => - collection.magicMetadata?.data.subType === SUB_TYPE.QUICK_LINK_COLLECTION; + collection.magicMetadata?.data.subType == CollectionSubType.quicklink; export function isIncomingViewerShare(collection: Collection, user: User) { const sharee = collection.sharees?.find((sharee) => sharee.id === user.id); - return sharee?.role === COLLECTION_ROLE.VIEWER; + return sharee?.role == "VIEWER"; } export function isValidMoveTarget( @@ -337,9 +328,9 @@ export function isValidReplacementAlbum( ) { return ( collection.name === wantedCollectionName && - (collection.type === CollectionType.album || - collection.type === CollectionType.folder || - collection.type === CollectionType.uncategorized) && + (collection.type == "album" || + collection.type == "folder" || + collection.type == "uncategorized") && !isHiddenCollection(collection) && !isQuickLinkCollection(collection) && !isIncomingShare(collection, user) @@ -350,7 +341,7 @@ export const getOrCreateAlbum = async ( albumName: string, existingCollections: Collection[], ) => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (!user?.id) { throw Error("user missing"); } diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 8e92807c97..4d87dad5a5 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -17,7 +17,7 @@ import { FileType } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import { deleteFromTrash, moveToTrash } from "@/new/photos/services/collection"; import { safeFileName } from "@/new/photos/utils/native-fs"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { t } from "i18next"; import { @@ -30,16 +30,15 @@ import { SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; -export enum FILE_OPS_TYPE { - DOWNLOAD, - FIX_TIME, - ARCHIVE, - UNARCHIVE, - HIDE, - TRASH, - DELETE_PERMANENTLY, - SET_FAVORITE, -} +export type FileOp = + | "download" + | "fixTime" + | "favorite" + | "archive" + | "unarchive" + | "hide" + | "trash" + | "deletePermanently"; export async function downloadFile(file: EnteFile) { try { @@ -317,7 +316,7 @@ async function downloadFileDesktop( } export const isImageOrVideo = (fileType: FileType) => - [FileType.image, FileType.video].includes(fileType); + fileType == FileType.image || fileType == FileType.video; export const getArchivedFiles = (files: EnteFile[]) => { return files.filter(isArchivedFile).map((file) => file.id); @@ -329,7 +328,7 @@ export const createTypedObjectURL = async (blob: Blob, fileName: string) => { }; export const getUserOwnedFiles = (files: EnteFile[]) => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (!user?.id) { throw Error("user missing"); } @@ -375,8 +374,8 @@ export const shouldShowAvatar = (file: EnteFile, user: User) => { } }; -export const handleFileOps = async ( - ops: FILE_OPS_TYPE, +export const handleFileOp = async ( + op: FileOp, files: EnteFile[], markTempDeleted: (files: EnteFile[]) => void, clearTempDeleted: () => void, @@ -385,35 +384,8 @@ export const handleFileOps = async ( fixCreationTime: (files: EnteFile[]) => void, setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator, ) => { - switch (ops) { - case FILE_OPS_TYPE.TRASH: - try { - markTempDeleted(files); - await moveToTrash(files); - } catch (e) { - clearTempDeleted(); - throw e; - } - break; - case FILE_OPS_TYPE.DELETE_PERMANENTLY: - try { - markTempDeleted(files); - await deleteFromTrash(files.map((file) => file.id)); - } catch (e) { - clearTempDeleted(); - throw e; - } - break; - case FILE_OPS_TYPE.HIDE: - try { - markTempHidden(files); - await moveToHiddenCollection(files); - } catch (e) { - clearTempHidden(); - throw e; - } - break; - case FILE_OPS_TYPE.DOWNLOAD: { + switch (op) { + case "download": { const setSelectedFileDownloadProgressAttributes = setFilesDownloadProgressAttributesCreator( t("files_count", { count: files.length }), @@ -424,17 +396,44 @@ export const handleFileOps = async ( ); break; } - case FILE_OPS_TYPE.FIX_TIME: + case "fixTime": fixCreationTime(files); break; - case FILE_OPS_TYPE.ARCHIVE: + case "favorite": + await addMultipleToFavorites(files); + break; + case "archive": await changeFilesVisibility(files, ItemVisibility.archived); break; - case FILE_OPS_TYPE.UNARCHIVE: + case "unarchive": await changeFilesVisibility(files, ItemVisibility.visible); break; - case FILE_OPS_TYPE.SET_FAVORITE: - await addMultipleToFavorites(files); + case "hide": + try { + markTempHidden(files); + await moveToHiddenCollection(files); + } catch (e) { + clearTempHidden(); + throw e; + } + break; + case "trash": + try { + markTempDeleted(files); + await moveToTrash(files); + } catch (e) { + clearTempDeleted(); + throw e; + } + break; + case "deletePermanently": + try { + markTempDeleted(files); + await deleteFromTrash(files.map((file) => file.id)); + } catch (e) { + clearTempDeleted(); + throw e; + } break; } }; diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 4ae5094eba..89007cc56a 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -133,8 +133,10 @@ For showing the app's UI in multiple languages, we use the [i18next](https://www.i18next.com), specifically its three components - [i18next](https://github.com/i18next/i18next): The core `i18next` library. + - [react-i18next](https://github.com/i18next/react-i18next): React specific support in `i18next`. + - [i18next-http-backend](https://github.com/i18next/i18next-http-backend): Adds support for initializing `i18next` with JSON file containing the translation in a particular language, fetched at runtime. @@ -173,7 +175,9 @@ via [@fontsource-variable/inter](https://fontsource.org/fonts/inter/install). layer on top of web workers to make them more easier to use. - [idb](https://github.com/jakearchibald/idb) provides a promise API over the - browser-native IndexedDB APIs. + browser-native IndexedDB APIs. Older code (the file and collection store), + uses [localForage](https://github.com/localForage/localForage) for IndexedDB + access. > For more details about IDB and its role, see [storage.md](storage.md). diff --git a/web/docs/storage.md b/web/docs/storage.md index 0a1ed94d68..c5077bcd5a 100644 --- a/web/docs/storage.md +++ b/web/docs/storage.md @@ -41,6 +41,7 @@ For more details, see: - https://web.dev/articles/indexeddb - https://github.com/jakearchibald/idb +- https://github.com/localForage/localForage ## OPFS diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index 88c6456e75..17e01da37b 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -2,7 +2,6 @@ import { AccountsPageFooter, AccountsPageTitle, } from "@/accounts/components/layouts/centered-paper"; -import { PAGES } from "@/accounts/constants/pages"; import { getSRPAttributes } from "@/accounts/services/srp-remote"; import { sendOTT } from "@/accounts/services/user"; import { LinkButton } from "@/base/components/LinkButton"; @@ -11,7 +10,7 @@ import log from "@/base/log"; import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; -import { LS_KEYS, setData, setLSUser } from "@ente/shared/storage/localStorage"; +import { setData, setLSUser } from "@ente/shared/storage/localStorage"; import { Input, Stack, Typography } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -54,11 +53,11 @@ export const LoginContents: React.FC = ({ throw e; } await setLSUser({ email }); - void router.push(PAGES.VERIFY); + void router.push("/verify"); } else { await setLSUser({ email }); - setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes); - void router.push(PAGES.CREDENTIALS); + setData("srpAttributes", srpAttributes); + void router.push("/credentials"); } } catch (e) { log.error("Login failed", e); diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index a8d9061de2..270c4fb7b3 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -1,4 +1,3 @@ -import { PAGES } from "@/accounts/constants/pages"; import { generateKeyAndSRPAttributes } from "@/accounts/services/srp"; import { sendOTT } from "@/accounts/services/user"; import { isWeakPassword } from "@/accounts/utils/password"; @@ -6,7 +5,7 @@ import { LinkButton } from "@/base/components/LinkButton"; import { LoadingButton } from "@/base/components/mui/LoadingButton"; import { isMuseumHTTPError } from "@/base/http"; import log from "@/base/log"; -import { LS_KEYS, setLSUser } from "@ente/shared//storage/localStorage"; +import { setLSUser } from "@ente/shared//storage/localStorage"; import { VerticallyCentered } from "@ente/shared/components/Container"; import ShowHidePassword from "@ente/shared/components/Form/ShowHidePassword"; import { @@ -18,7 +17,6 @@ import { setJustSignedUp, setLocalReferralSource, } from "@ente/shared/storage/localStorage/helpers"; -import { SESSION_KEYS } from "@ente/shared/storage/sessionStorage"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Box, @@ -109,20 +107,17 @@ export const SignUpContents: React.FC = ({ const { keyAttributes, masterKey, srpSetupAttributes } = await generateKeyAndSRPAttributes(passphrase); - setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes); - setData(LS_KEYS.SRP_SETUP_ATTRIBUTES, srpSetupAttributes); + setData("originalKeyAttributes", keyAttributes); + setData("srpSetupAttributes", srpSetupAttributes); await generateAndSaveIntermediateKeyAttributes( passphrase, keyAttributes, masterKey, ); - await saveKeyInSessionStore( - SESSION_KEYS.ENCRYPTION_KEY, - masterKey, - ); + await saveKeyInSessionStore("encryptionKey", masterKey); setJustSignedUp(true); - void router.push(PAGES.VERIFY); + void router.push("/verify"); } catch (e) { setFieldError("confirm", t("password_generation_failed")); throw e; diff --git a/web/packages/accounts/constants/pages.ts b/web/packages/accounts/constants/pages.ts deleted file mode 100644 index ea4896142f..0000000000 --- a/web/packages/accounts/constants/pages.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum PAGES { - ROOT = "/", - CHANGE_PASSWORD = "/change-password", - CREDENTIALS = "/credentials", - GENERATE = "/generate", - LOGIN = "/login", - RECOVER = "/recover", - SIGNUP = "/signup", - TWO_FACTOR_SETUP = "/two-factor/setup", - TWO_FACTOR_VERIFY = "/two-factor/verify", - TWO_FACTOR_RECOVER = "/two-factor/recover", - // PASSKEY_RECOVER = "/passkeys/recover", - VERIFY = "/verify", - SHARED_ALBUMS = "/shared-albums", -} diff --git a/web/packages/accounts/pages/change-email.tsx b/web/packages/accounts/pages/change-email.tsx index 8d372dc5a6..8196739fe4 100644 --- a/web/packages/accounts/pages/change-email.tsx +++ b/web/packages/accounts/pages/change-email.tsx @@ -10,7 +10,7 @@ import { LoadingButton } from "@/base/components/mui/LoadingButton"; import { isHTTPErrorWithStatus } from "@/base/http"; import log from "@/base/log"; import { VerticallyCentered } from "@ente/shared/components/Container"; -import { LS_KEYS, getData, setLSUser } from "@ente/shared/storage/localStorage"; +import { getData, setLSUser } from "@ente/shared/storage/localStorage"; import { Alert, Box, TextField } from "@mui/material"; import { Formik, type FormikHelpers } from "formik"; import { t } from "i18next"; @@ -23,7 +23,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user = getData(LS_KEYS.USER); + const user = getData("user"); if (!user?.token) { void router.push("/"); } @@ -86,7 +86,7 @@ const ChangeEmailForm: React.FC = () => { try { setLoading(true); await changeEmail(email, ott!); - await setLSUser({ ...getData(LS_KEYS.USER), email }); + await setLSUser({ ...getData("user"), email }); setLoading(false); void goToApp(); } catch (e) { diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index e19d6aff7a..f7be5204d7 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -6,7 +6,6 @@ import { import SetPasswordForm, { type SetPasswordFormProps, } from "@/accounts/components/SetPasswordForm"; -import { PAGES } from "@/accounts/constants/pages"; import { appHomeRoute, stashRedirect } from "@/accounts/services/redirect"; import { convertBase64ToBuffer, @@ -27,8 +26,7 @@ import { generateLoginSubKey, saveKeyInSessionStore, } from "@ente/shared/crypto/helpers"; -import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; -import { SESSION_KEYS } from "@ente/shared/storage/sessionStorage"; +import { getData, setData } from "@ente/shared/storage/localStorage"; import { getActualKey } from "@ente/shared/user"; import type { KEK, KeyAttributes, User } from "@ente/shared/user/types"; import { t } from "i18next"; @@ -42,10 +40,10 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user = getData(LS_KEYS.USER); + const user = getData("user"); setUser(user); if (!user?.token) { - stashRedirect(PAGES.CHANGE_PASSWORD); + stashRedirect("/change-password"); void router.push("/"); } else { setToken(user.token); @@ -58,7 +56,7 @@ const Page: React.FC = () => { ) => { const cryptoWorker = await sharedCryptoWorker(); const key = await getActualKey(); - const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes: KeyAttributes = getData("keyAttributes"); const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); let kek: KEK; try { @@ -113,7 +111,7 @@ const Page: React.FC = () => { if (user?.email) { const srpAttributes = await getSRPAttributes(user.email); if (srpAttributes) { - setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes); + setData("srpAttributes", srpAttributes); } } @@ -124,13 +122,13 @@ const Page: React.FC = () => { key, ); - await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key); + await saveKeyInSessionStore("encryptionKey", key); redirectToAppHome(); }; const redirectToAppHome = () => { - setData(LS_KEYS.SHOW_BACK_BUTTON, { value: true }); + setData("showBackButton", { value: true }); void router.push(appHomeRoute); }; @@ -143,7 +141,7 @@ const Page: React.FC = () => { callback={onSubmit} buttonText={t("change_password")} /> - {(getData(LS_KEYS.SHOW_BACK_BUTTON)?.value ?? true) && ( + {(getData("showBackButton")?.value ?? true) && ( {t("go_back")} diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 922d36a477..cd483753fb 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -7,7 +7,6 @@ import { import { SecondFactorChoice } from "@/accounts/components/SecondFactorChoice"; import { sessionExpiredDialogAttributes } from "@/accounts/components/utils/dialog"; import { useSecondFactorChoiceIfNeeded } from "@/accounts/components/utils/second-factor-choice"; -import { PAGES } from "@/accounts/constants/pages"; import { openPasskeyVerificationURL, passkeyVerificationRedirectURL, @@ -42,23 +41,13 @@ import { saveKeyInSessionStore, } from "@ente/shared/crypto/helpers"; import { CustomError } from "@ente/shared/error"; -import { - LS_KEYS, - getData, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { getToken, isFirstLogin, setIsFirstLogin, } from "@ente/shared/storage/localStorage/helpers"; -import { - SESSION_KEYS, - getKey, - removeKey, - setKey, -} from "@ente/shared/storage/sessionStorage"; +import { getKey, removeKey, setKey } from "@ente/shared/storage/sessionStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -96,14 +85,8 @@ const Page: React.FC = () => { case "valid": break; case "validButPasswordChanged": - setData( - LS_KEYS.KEY_ATTRIBUTES, - session.updatedKeyAttributes, - ); - setData( - LS_KEYS.SRP_ATTRIBUTES, - session.updatedSRPAttributes, - ); + setData("keyAttributes", session.updatedKeyAttributes); + setData("srpAttributes", session.updatedSRPAttributes); // Set a flag that causes new interactive key attributes to // be generated. setIsFirstLogin(true); @@ -121,13 +104,13 @@ const Page: React.FC = () => { useEffect(() => { const main = async () => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (!user?.email) { void router.push("/"); return; } setUser(user); - let key = getKey(SESSION_KEYS.ENCRYPTION_KEY); + let key = getKey("encryptionKey"); const electron = globalThis.electron; if (!key && electron) { try { @@ -136,11 +119,7 @@ const Page: React.FC = () => { log.error("Failed to read master key from safe storage", e); } if (key) { - await saveKeyInSessionStore( - SESSION_KEYS.ENCRYPTION_KEY, - key, - true, - ); + await saveKeyInSessionStore("encryptionKey", key, true); } } const token = getToken(); @@ -148,22 +127,17 @@ const Page: React.FC = () => { void router.push(appHomeRoute); return; } - const kekEncryptedAttributes: B64EncryptionResult = getKey( - SESSION_KEYS.KEY_ENCRYPTION_KEY, - ); - const keyAttributes: KeyAttributes = getData( - LS_KEYS.KEY_ATTRIBUTES, - ); - const srpAttributes: SRPAttributes = getData( - LS_KEYS.SRP_ATTRIBUTES, - ); + const kekEncryptedAttributes: B64EncryptionResult = + getKey("keyEncryptionKey"); + const keyAttributes: KeyAttributes = getData("keyAttributes"); + const srpAttributes: SRPAttributes = getData("srpAttributes"); if (token) { setSessionValidityCheck(validateSession()); } if (kekEncryptedAttributes && keyAttributes) { - removeKey(SESSION_KEYS.KEY_ENCRYPTION_KEY); + removeKey("keyEncryptionKey"); const cryptoWorker = await sharedCryptoWorker(); const kek = await cryptoWorker.decryptB64( kekEncryptedAttributes.encryptedData, @@ -232,11 +206,8 @@ const Page: React.FC = () => { if (passkeySessionID) { const sessionKeyAttributes = await cryptoWorker.generateKeyAndEncryptToB64(kek); - setKey( - SESSION_KEYS.KEY_ENCRYPTION_KEY, - sessionKeyAttributes, - ); - const user = getData(LS_KEYS.USER); + setKey("keyEncryptionKey", sessionKeyAttributes); + const user = getData("user"); await setLSUser({ ...user, passkeySessionID, @@ -254,20 +225,17 @@ const Page: React.FC = () => { } else if (twoFactorSessionID) { const sessionKeyAttributes = await cryptoWorker.generateKeyAndEncryptToB64(kek); - setKey( - SESSION_KEYS.KEY_ENCRYPTION_KEY, - sessionKeyAttributes, - ); - const user = getData(LS_KEYS.USER); + setKey("keyEncryptionKey", sessionKeyAttributes); + const user = getData("user"); await setLSUser({ ...user, twoFactorSessionID, isTwoFactorEnabled: true, }); - void router.push(PAGES.TWO_FACTOR_VERIFY); + void router.push("/two-factor/verify"); throw Error(CustomError.TWO_FACTOR_ENABLED); } else { - const user = getData(LS_KEYS.USER); + const user = getData("user"); await setLSUser({ ...user, token, @@ -275,8 +243,7 @@ const Page: React.FC = () => { id, isTwoFactorEnabled: false, }); - if (keyAttributes) - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); + if (keyAttributes) setData("keyAttributes", keyAttributes); return keyAttributes; } } catch (e) { @@ -305,16 +272,15 @@ const Page: React.FC = () => { key, ); } - await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, key); + await saveKeyInSessionStore("encryptionKey", key); await decryptAndStoreToken(keyAttributes, key); try { - let srpAttributes: SRPAttributes | null = getData( - LS_KEYS.SRP_ATTRIBUTES, - ); + let srpAttributes: SRPAttributes | null = + getData("srpAttributes"); if (!srpAttributes && user) { srpAttributes = await getSRPAttributes(user.email); if (srpAttributes) { - setData(LS_KEYS.SRP_ATTRIBUTES, srpAttributes); + setData("srpAttributes", srpAttributes); } } log.debug(() => `userSRPSetupPending ${!srpAttributes}`); @@ -379,7 +345,7 @@ const Page: React.FC = () => { /> - router.push(PAGES.RECOVER)}> + router.push("/recover")}> {t("forgot_password")} {t("change_email")} diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index 8010e7e378..e030bc9246 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -7,7 +7,6 @@ import { RecoveryKey } from "@/accounts/components/RecoveryKey"; import SetPasswordForm, { type SetPasswordFormProps, } from "@/accounts/components/SetPasswordForm"; -import { PAGES } from "@/accounts/constants/pages"; import { appHomeRoute } from "@/accounts/services/redirect"; import { configureSRP, @@ -22,12 +21,12 @@ import { generateAndSaveIntermediateKeyAttributes, saveKeyInSessionStore, } from "@ente/shared/crypto/helpers"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { justSignedUp, setJustSignedUp, } from "@ente/shared/storage/localStorage/helpers"; -import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage"; +import { getKey } from "@ente/shared/storage/sessionStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -44,11 +43,9 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const key: string = getKey(SESSION_KEYS.ENCRYPTION_KEY); - const keyAttributes: KeyAttributes = getData( - LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, - ); - const user: User = getData(LS_KEYS.USER); + const key: string = getKey("encryptionKey"); + const keyAttributes: KeyAttributes = getData("originalKeyAttributes"); + const user: User = getData("user"); setUser(user); if (!user?.token) { void router.push("/"); @@ -60,7 +57,7 @@ const Page: React.FC = () => { void router.push(appHomeRoute); } } else if (keyAttributes?.encryptedKey) { - void router.push(PAGES.CREDENTIALS); + void router.push("/credentials"); } else { setToken(user.token); setLoading(false); @@ -83,7 +80,7 @@ const Page: React.FC = () => { keyAttributes, masterKey, ); - await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, masterKey); + await saveKeyInSessionStore("encryptionKey", masterKey); setJustSignedUp(true); setOpenRecoveryKey(true); } catch (e) { diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index a4927b61c9..b8d47cdf9c 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -1,9 +1,8 @@ import { AccountsPageContents } from "@/accounts/components/layouts/centered-paper"; import { LoginContents } from "@/accounts/components/LoginContents"; -import { PAGES } from "@/accounts/constants/pages"; import { LoadingIndicator } from "@/base/components/loaders"; import { customAPIHost } from "@/base/origins"; -import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; @@ -15,14 +14,14 @@ const Page: React.FC = () => { useEffect(() => { void customAPIHost().then(setHost); - const user = getData(LS_KEYS.USER); + const user = getData("user"); if (user?.email) { - void router.push(PAGES.VERIFY); + void router.push("/verify"); } setLoading(false); }, [router]); - const onSignUp = () => void router.push(PAGES.SIGNUP); + const onSignUp = () => void router.push("/signup"); return loading ? ( diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 6babae304f..c6d667e78e 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -1,15 +1,9 @@ -import { PAGES } from "@/accounts/constants/pages"; import { unstashRedirect } from "@/accounts/services/redirect"; import { LoadingIndicator } from "@/base/components/loaders"; import { fromB64URLSafeNoPaddingString } from "@/base/crypto/libsodium"; import log from "@/base/log"; import { nullToUndefined } from "@/utils/transform"; -import { - LS_KEYS, - getData, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { useRouter } from "next/router"; import React, { useEffect } from "react"; @@ -96,8 +90,8 @@ const saveCredentialsAndNavigateTo = async ( // user is signing into an existing account). const { keyAttributes, encryptedToken, token, id } = decodedResponse; - await setLSUser({ ...getData(LS_KEYS.USER), token, encryptedToken, id }); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); + await setLSUser({ ...getData("user"), token, encryptedToken, id }); + setData("keyAttributes", keyAttributes); - return unstashRedirect() ?? PAGES.CREDENTIALS; + return unstashRedirect() ?? "/credentials"; }; diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 7c69ea44a6..a1fd57d8da 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -3,7 +3,6 @@ import { AccountsPageFooter, AccountsPageTitle, } from "@/accounts/components/layouts/centered-paper"; -import { PAGES } from "@/accounts/constants/pages"; import { appHomeRoute, stashRedirect } from "@/accounts/services/redirect"; import { sendOTT } from "@/accounts/services/user"; import { LinkButton } from "@/base/components/LinkButton"; @@ -17,8 +16,8 @@ import { decryptAndStoreToken, saveKeyInSessionStore, } from "@ente/shared/crypto/helpers"; -import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; -import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage"; +import { getData, setData } from "@ente/shared/storage/localStorage"; +import { getKey } from "@ente/shared/storage/sessionStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -39,21 +38,21 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user: User = getData(LS_KEYS.USER); - const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); - const key = getKey(SESSION_KEYS.ENCRYPTION_KEY); + const user: User = getData("user"); + const keyAttributes: KeyAttributes = getData("keyAttributes"); + const key = getKey("encryptionKey"); if (!user?.email) { void router.push("/"); return; } if (!user?.encryptedToken && !user?.token) { void sendOTT(user.email, undefined); - stashRedirect(PAGES.RECOVER); - void router.push(PAGES.VERIFY); + stashRedirect("/recover"); + void router.push("/verify"); return; } if (!keyAttributes) { - void router.push(PAGES.GENERATE); + void router.push("/generate"); } else if (key) { void router.push(appHomeRoute); } else { @@ -86,11 +85,11 @@ const Page: React.FC = () => { keyAttr.masterKeyDecryptionNonce!, await cryptoWorker.fromHex(recoveryKey), ); - await saveKeyInSessionStore(SESSION_KEYS.ENCRYPTION_KEY, masterKey); + await saveKeyInSessionStore("encryptionKey", masterKey); await decryptAndStoreToken(keyAttr, masterKey); - setData(LS_KEYS.SHOW_BACK_BUTTON, { value: false }); - void router.push(PAGES.CHANGE_PASSWORD); + setData("showBackButton", { value: false }); + void router.push("/change-password"); } catch (e) { log.error("password recovery failed", e); setFieldError(t("incorrect_recovery_key")); diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index 4920053c24..86a43703ed 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -1,9 +1,8 @@ import { AccountsPageContents } from "@/accounts/components/layouts/centered-paper"; import { SignUpContents } from "@/accounts/components/SignUpContents"; -import { PAGES } from "@/accounts/constants/pages"; import { LoadingIndicator } from "@/base/components/loaders"; import { customAPIHost } from "@/base/origins"; -import { LS_KEYS, getData } from "@ente/shared//storage/localStorage"; +import { getData } from "@ente/shared//storage/localStorage"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; @@ -15,14 +14,14 @@ const Page: React.FC = () => { useEffect(() => { void customAPIHost().then(setHost); - const user = getData(LS_KEYS.USER); + const user = getData("user"); if (user?.email) { - void router.push(PAGES.VERIFY); + void router.push("/verify"); } setLoading(false); }, [router]); - const onLogin = () => void router.push(PAGES.LOGIN); + const onLogin = () => void router.push("/login"); return loading ? ( diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 6f6f0e43c8..cb3e6148dc 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -3,7 +3,6 @@ import { AccountsPageFooter, AccountsPageTitle, } from "@/accounts/components/layouts/centered-paper"; -import { PAGES } from "@/accounts/constants/pages"; import { recoverTwoFactor, removeTwoFactor, @@ -19,12 +18,7 @@ import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import { ApiError } from "@ente/shared/error"; -import { - LS_KEYS, - getData, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { Link } from "@mui/material"; import { HttpStatusCode } from "axios"; import { t } from "i18next"; @@ -53,7 +47,7 @@ const Page: React.FC = ({ twoFactorType }) => { const router = useRouter(); useEffect(() => { - const user = getData(LS_KEYS.USER); + const user = getData("user"); const sid = user.passkeySessionID || user.twoFactorSessionID; if (!user?.email || !sid) { void router.push("/"); @@ -61,7 +55,7 @@ const Page: React.FC = ({ twoFactorType }) => { !(user.isTwoFactorEnabled || user.isTwoFactorEnabledPasskey) && (user.encryptedToken || user.token) ) { - void router.push(PAGES.GENERATE); + void router.push("/generate"); } else { setSessionID(sid); } @@ -128,14 +122,14 @@ const Page: React.FC = ({ twoFactorType }) => { ); const { keyAttributes, encryptedToken, token, id } = resp; await setLSUser({ - ...getData(LS_KEYS.USER), + ...getData("user"), token, encryptedToken, id, isTwoFactorEnabled: false, }); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); - void router.push(PAGES.CREDENTIALS); + setData("keyAttributes", keyAttributes); + void router.push("/credentials"); } catch (e) { log.error("two factor recovery failed", e); setFieldError(t("incorrect_recovery_key")); diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index c1333d3e55..bb24c291e0 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -8,7 +8,7 @@ import { LinkButton } from "@/base/components/LinkButton"; import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton"; import { encryptWithRecoveryKey } from "@ente/shared/crypto/helpers"; -import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage"; +import { getData, setLSUser } from "@ente/shared/storage/localStorage"; import { Paper, Stack, styled, Typography } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -35,7 +35,7 @@ const Page: React.FC = () => { encryptedTwoFactorSecret, twoFactorSecretDecryptionNonce, }); - await setLSUser({ ...getData(LS_KEYS.USER), isTwoFactorEnabled: true }); + await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); await router.push(appHomeRoute); }; diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index 8434969600..fce7f7bcd7 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -1,15 +1,9 @@ import { Verify2FACodeForm } from "@/accounts/components/Verify2FACodeForm"; -import { PAGES } from "@/accounts/constants/pages"; import { verifyTwoFactor } from "@/accounts/services/user"; import { LinkButton } from "@/base/components/LinkButton"; import { useBaseContext } from "@/base/context"; import { HTTPError } from "@/base/http"; -import { - LS_KEYS, - getData, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import type { User } from "@ente/shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -29,14 +23,14 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); if (!user?.email || !user.twoFactorSessionID) { void router.push("/"); } else if ( !user.isTwoFactorEnabled && (user.encryptedToken || user.token) ) { - void router.push(PAGES.CREDENTIALS); + void router.push("/credentials"); } else { setSessionID(user.twoFactorSessionID); } @@ -46,14 +40,9 @@ const Page: React.FC = () => { try { const resp = await verifyTwoFactor(otp, sessionID); const { keyAttributes, encryptedToken, token, id } = resp; - await setLSUser({ - ...getData(LS_KEYS.USER), - token, - encryptedToken, - id, - }); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes!); - await router.push(unstashRedirect() ?? PAGES.CREDENTIALS); + await setLSUser({ ...getData("user"), token, encryptedToken, id }); + setData("keyAttributes", keyAttributes!); + await router.push(unstashRedirect() ?? "/credentials"); } catch (e) { if (e instanceof HTTPError && e.res.status == 404) { logout(); @@ -71,9 +60,7 @@ const Page: React.FC = () => { submitButtonText={t("verify")} /> - router.push(PAGES.TWO_FACTOR_RECOVER)} - > + router.push("/two-factor/recover")}> {t("lost_2fa_device")} {t("change_email")} diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index d96d6b9009..41de21a019 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -6,7 +6,6 @@ import { import { VerifyingPasskey } from "@/accounts/components/LoginComponents"; import { SecondFactorChoice } from "@/accounts/components/SecondFactorChoice"; import { useSecondFactorChoiceIfNeeded } from "@/accounts/components/utils/second-factor-choice"; -import { PAGES } from "@/accounts/constants/pages"; import { openPasskeyVerificationURL, passkeyVerificationRedirectURL, @@ -28,12 +27,7 @@ import SingleInputForm, { } from "@ente/shared/components/SingleInputForm"; import { ApiError } from "@ente/shared/error"; import localForage from "@ente/shared/storage/localForage"; -import { - getData, - LS_KEYS, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { getLocalReferralSource, setIsFirstLogin, @@ -64,7 +58,7 @@ const Page: React.FC = () => { useEffect(() => { const main = async () => { - const user: User = getData(LS_KEYS.USER); + const user: User = getData("user"); const redirect = await redirectionIfNeeded(user); if (redirect) { @@ -97,7 +91,7 @@ const Page: React.FC = () => { await verifyEmail(email, ott, cleanedReferral), ); if (passkeySessionID) { - const user = getData(LS_KEYS.USER); + const user = getData("user"); await setLSUser({ ...user, passkeySessionID, @@ -123,7 +117,7 @@ const Page: React.FC = () => { isTwoFactorEnabled: true, }); setIsFirstLogin(true); - void router.push(PAGES.TWO_FACTOR_VERIFY); + void router.push("/two-factor/verify"); } else { await setLSUser({ email, @@ -133,19 +127,18 @@ const Page: React.FC = () => { isTwoFactorEnabled: false, }); if (keyAttributes) { - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes); - setData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES, keyAttributes); + setData("keyAttributes", keyAttributes); + setData("originalKeyAttributes", keyAttributes); } else { - if (getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES)) { + if (getData("originalKeyAttributes")) { await putAttributes( token!, - getData(LS_KEYS.ORIGINAL_KEY_ATTRIBUTES), + getData("originalKeyAttributes"), ); } - if (getData(LS_KEYS.SRP_SETUP_ATTRIBUTES)) { - const srpSetupAttributes: SRPSetupAttributes = getData( - LS_KEYS.SRP_SETUP_ATTRIBUTES, - ); + if (getData("srpSetupAttributes")) { + const srpSetupAttributes: SRPSetupAttributes = + getData("srpSetupAttributes"); await configureSRP(srpSetupAttributes); } } @@ -154,9 +147,9 @@ const Page: React.FC = () => { const redirectURL = unstashRedirect(); if (keyAttributes?.encryptedKey) { clearKeys(); - void router.push(redirectURL ?? PAGES.CREDENTIALS); + void router.push(redirectURL ?? "/credentials"); } else { - void router.push(redirectURL ?? PAGES.GENERATE); + void router.push(redirectURL ?? "/generate"); } } } catch (e) { @@ -272,14 +265,14 @@ const redirectionIfNeeded = async (user: User | undefined) => { return "/"; } - const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes: KeyAttributes = getData("keyAttributes"); if (keyAttributes?.encryptedKey && (user.token || user.encryptedToken)) { - return PAGES.CREDENTIALS; + return "/credentials"; } // If we're coming here during the recover flow, do not redirect. - if (stashedRedirect() == PAGES.RECOVER) return undefined; + if (stashedRedirect() == "/recover") return undefined; // The user might have email verification disabled, but after previously // entering their email on the login screen, they might've closed the tab @@ -292,14 +285,14 @@ const redirectionIfNeeded = async (user: User | undefined) => { // saved them). If they are present and indicate that email verification is // not required, redirect to the password verification page. - const srpAttributes: SRPAttributes = getData(LS_KEYS.SRP_ATTRIBUTES); + const srpAttributes: SRPAttributes = getData("srpAttributes"); if (srpAttributes && !srpAttributes.isEmailMFAEnabled) { // Fetch the latest SRP attributes instead of relying on the potentially // stale stored values. This is an infrequent scenario path, so extra // API calls are fine. const latestSRPAttributes = await getSRPAttributes(email); if (latestSRPAttributes && !latestSRPAttributes.isEmailMFAEnabled) { - return PAGES.CREDENTIALS; + return "/credentials"; } } diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 4094c7d4bf..8fe0cc7c6c 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -12,12 +12,7 @@ import log from "@/base/log"; import { apiURL } from "@/base/origins"; import { getRecoveryKey } from "@ente/shared/crypto/helpers"; import HTTPService from "@ente/shared/network/HTTPService"; -import { - getData, - LS_KEYS, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { z } from "zod"; import { unstashRedirect } from "./redirect"; @@ -265,8 +260,8 @@ export const saveCredentialsAndNavigateTo = async ( // /passkeys/finish page. const { id, encryptedToken, keyAttributes } = response; - await setLSUser({ ...getData(LS_KEYS.USER), encryptedToken, id }); - setData(LS_KEYS.KEY_ATTRIBUTES, keyAttributes!); + await setLSUser({ ...getData("user"), encryptedToken, id }); + setData("keyAttributes", keyAttributes!); return unstashRedirect() ?? "/credentials"; }; diff --git a/web/packages/accounts/services/redirect.ts b/web/packages/accounts/services/redirect.ts index 57a54b022c..ec748a6e6e 100644 --- a/web/packages/accounts/services/redirect.ts +++ b/web/packages/accounts/services/redirect.ts @@ -1,5 +1,4 @@ import { appName } from "@/base/app"; -import { AUTH_PAGES, PHOTOS_PAGES } from "@ente/shared/constants/pages"; /** * The default page ("home route") for each of our apps. @@ -8,9 +7,9 @@ import { AUTH_PAGES, PHOTOS_PAGES } from "@ente/shared/constants/pages"; */ export const appHomeRoute: string = { accounts: "/passkeys", - auth: AUTH_PAGES.AUTH, + auth: "/auth", cast: "/" /* The cast app doesn't use this, this is an arbitrary value. */, - photos: PHOTOS_PAGES.GALLERY, + photos: "/gallery", }[appName]; let _stashedRedirect: string | undefined; diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 97c88c16f7..ee2a310beb 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -1,7 +1,7 @@ import { authenticatedRequestHeaders, HTTPError } from "@/base/http"; import { ensureLocalUser } from "@/base/local-user"; import { apiURL } from "@/base/origins"; -import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import type { KeyAttributes } from "@ente/shared/user/types"; import type { SRPAttributes } from "./srp-remote"; import { getSRPAttributes } from "./srp-remote"; @@ -85,7 +85,7 @@ export const checkSessionValidity = async (): Promise => { // We should have these values locally if we reach here. const email = ensureLocalUser().email; - const localSRPAttributes = getData(LS_KEYS.SRP_ATTRIBUTES)!; + const localSRPAttributes = getData("srpAttributes")!; // Fetch the remote SRP attributes. // diff --git a/web/packages/base/local-user.ts b/web/packages/base/local-user.ts index c2a446eb3c..9bde1583a4 100644 --- a/web/packages/base/local-user.ts +++ b/web/packages/base/local-user.ts @@ -29,7 +29,7 @@ export type LocalUser = z.infer; * The user's data is stored in the browser's localStorage. */ export const localUser = (): LocalUser | undefined => { - // TODO(MR): duplicate of LS_KEYS.USER + // TODO: duplicate of getData("user") const s = localStorage.getItem("user"); if (!s) return undefined; return LocalUser.parse(JSON.parse(s)); diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index b3c2e7fd82..b416b8ed9a 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -33,7 +33,7 @@ export const haveCredentialsInSession = () => * logged in, otherwise return `undefined`. */ export const masterKeyFromSessionIfLoggedIn = async () => { - // TODO: Same value as the deprecated SESSION_KEYS.ENCRYPTION_KEY. + // TODO: Same value as the deprecated getKey("encryptionKey") const value = sessionStorage.getItem("encryptionKey"); if (!value) return undefined; diff --git a/web/packages/build-config/tsconfig-next.json b/web/packages/build-config/tsconfig-next.json index a8257b0bba..af9dafc21a 100644 --- a/web/packages/build-config/tsconfig-next.json +++ b/web/packages/build-config/tsconfig-next.json @@ -2,7 +2,8 @@ /* A base TSConfig for typechecking our Next.js apps and packages. */ "extends": "@/build-config/tsconfig-typecheck.json", "compilerOptions": { - /* Next.js insists on adding these. Sigh. */ + /* Next.js forcefully insists on adding these, which prevents us from + enabling the isolatedDeclarations option. Sigh. */ "allowJs": true, "incremental": true, /* MUI doesn't work with exactOptionalPropertyTypes yet. */ diff --git a/web/packages/gallery/components/FileInfo.tsx b/web/packages/gallery/components/FileInfo.tsx index 2f5fb2dfce..076c283dde 100644 --- a/web/packages/gallery/components/FileInfo.tsx +++ b/web/packages/gallery/components/FileInfo.tsx @@ -128,10 +128,10 @@ export type FileInfoProps = ModalVisibilityProps & { */ allowMap?: boolean; /** - * If set, then a clickable chip will be shown for each collection that this - * file is a part of. + * If set, then a clickable chip will be shown for each normal collection + * that this file is a part of. * - * Uses {@link fileCollectionIDs}, {@link allCollectionsNameByID} and + * Uses {@link fileCollectionIDs}, {@link collectionNameByID} and * {@link onSelectCollection}, so all of those props should also be set for * this to have an effect. */ @@ -147,7 +147,7 @@ export type FileInfoProps = ModalVisibilityProps & { * * Used when {@link showCollections} is set. */ - allCollectionsNameByID?: Map; + collectionNameByID?: Map; /** * Called when the action on the file info drawer has changed some the * metadata for some file, and we need to sync with remote to get our @@ -189,7 +189,7 @@ export const FileInfo: React.FC = ({ allowMap, showCollections, fileCollectionIDs, - allCollectionsNameByID, + collectionNameByID, onNeedsRemoteSync, onUpdateCaption, onSelectCollection, @@ -339,13 +339,13 @@ export const FileInfo: React.FC = ({ )} {showCollections && fileCollectionIDs && - allCollectionsNameByID && + collectionNameByID && onSelectCollection && ( @@ -1003,14 +1003,14 @@ const ExifItem = styled("div")` type AlbumsProps = Required< Pick< FileInfoProps, - "fileCollectionIDs" | "allCollectionsNameByID" | "onSelectCollection" + "fileCollectionIDs" | "collectionNameByID" | "onSelectCollection" > > & { file: EnteFile }; const Albums: React.FC = ({ file, fileCollectionIDs, - allCollectionsNameByID, + collectionNameByID, onSelectCollection, }) => ( }> @@ -1025,15 +1025,13 @@ const Albums: React.FC = ({ > {fileCollectionIDs .get(file.id) - ?.filter((collectionID) => - allCollectionsNameByID.has(collectionID), - ) + ?.filter((collectionID) => collectionNameByID.has(collectionID)) .map((collectionID) => ( onSelectCollection(collectionID)} > - {allCollectionsNameByID.get(collectionID)} + {collectionNameByID.get(collectionID)} ))} diff --git a/web/packages/gallery/components/viewer/FileViewer.tsx b/web/packages/gallery/components/viewer/FileViewer.tsx index a01d3db249..643eb620bf 100644 --- a/web/packages/gallery/components/viewer/FileViewer.tsx +++ b/web/packages/gallery/components/viewer/FileViewer.tsx @@ -8,6 +8,7 @@ import { LoadingButton } from "@/base/components/mui/LoadingButton"; import { useIsSmallWidth } from "@/base/components/utils/hooks"; import { type ModalVisibilityProps } from "@/base/components/utils/modal"; import { useBaseContext } from "@/base/context"; +import { isDevBuild } from "@/base/env"; import { lowercaseExtension } from "@/base/file-name"; import { formattedListJoin, ut } from "@/base/i18n"; import type { LocalUser } from "@/base/local-user"; @@ -53,6 +54,7 @@ import React, { useRef, useState, } from "react"; +import { hlsPlaylistForFile } from "../../services/video"; import { fileInfoExifForFile, updateItemDataAlt, @@ -175,17 +177,28 @@ export type FileViewerProps = ModalVisibilityProps & { /** * File IDs of all the files that the user has marked as a favorite. * - * If this is not provided then the favorite toggle button will not be shown - * in the file actions. + * See also {@link onToggleFavorite}. */ favoriteFileIDs?: Set; + /** + * File IDs of for which an update of its favorite status is pending (e.g. + * due to a toggle favorite action in the file viewer). + * + * See also {@link favoriteFileIDs} and {@link onToggleFavorite}. + */ + pendingFavoriteUpdates?: Set; /** * File IDs of for which an update of its visibility is pending (e.g. due to * a toggle archived action in the file viewer). * - * See also {@link pendingVisibilityUpdates}. + * See also {@link onFileVisibilityUpdate}. */ pendingVisibilityUpdates?: Set; + /** + * A mapping from file IDs to the IDs of the normal (non-hidden) collections + * that they are a part of. + */ + fileNormalCollectionIDs?: FileInfoProps["fileCollectionIDs"]; /** * Called when there was some update performed within the file viewer that * necessitates us to sync with remote again to fetch the latest updates. @@ -212,12 +225,9 @@ export type FileViewerProps = ModalVisibilityProps & { * Called when the favorite status of given {@link file} should be toggled * from its current value. * - * If this is not provided then the favorite toggle button will not be shown - * in the file actions. - * - * See also {@link favoriteFileIDs}. - * - * See also: [Note: File viewer update and dispatch] + * The favorite toggle button is shown only if all three of + * {@link favoriteFileIDs}, {@link pendingFavoriteUpdates} and + * {@link onToggleFavorite} are provided. */ onToggleFavorite?: (file: EnteFile) => Promise; /** @@ -256,10 +266,7 @@ export type FileViewerProps = ModalVisibilityProps & { onSaveEditedImageCopy?: ImageEditorOverlayProps["onSaveEditedCopy"]; } & Pick< FileInfoProps, - | "fileCollectionIDs" - | "allCollectionsNameByID" - | "onSelectCollection" - | "onSelectPerson" + "collectionNameByID" | "onSelectCollection" | "onSelectPerson" >; /** @@ -276,15 +283,16 @@ export const FileViewer: React.FC = ({ isInTrashSection, isInHiddenSection, favoriteFileIDs, + pendingFavoriteUpdates, pendingVisibilityUpdates, - fileCollectionIDs, - allCollectionsNameByID, + fileNormalCollectionIDs, + collectionNameByID, onTriggerSyncWithRemote, onVisualFeedback, onToggleFavorite, + onFileVisibilityUpdate, onDownload, onDelete, - onFileVisibilityUpdate, onSelectCollection, onSelectPerson, onSaveEditedImageCopy, @@ -339,32 +347,9 @@ export const FileViewer: React.FC = ({ const [isFullscreen, setIsFullscreen] = useState(false); - // Callbacks to be invoked (only once) the next time we get an update to the - // `files` or `favoriteFileIDs` props. - // - // The callback is passed the latest values of the `files` prop. - // - // Both of those trace their way back to the same reducer, so they get - // updated in tandem. When we delete files, only the `files` prop gets - // updated, while when we toggle favoriets, only the `favoriteFileIDs` prop - // gets updated. - const [, setOnNextFilesOrFavoritesUpdate] = useState< - ((files: EnteFile[]) => void)[] - >([]); - // If `true`, then we need to trigger a sync with remote when we close. const [, setNeedsSync] = useState(false); - /** - * Add a callback to be fired (only once) the next time we get an update to - * the `files` prop. - */ - const awaitNextFilesOrFavoritesUpdate = useCallback( - (cb: (files: EnteFile[]) => void) => - setOnNextFilesOrFavoritesUpdate((cbs) => cbs.concat(cb)), - [], - ); - const handleNeedsRemoteSync = useCallback(() => setNeedsSync(true), []); const handleClose = useCallback(() => { @@ -535,6 +520,15 @@ export const FileViewer: React.FC = ({ } })(); + if ( + isDevBuild && + process.env.NEXT_PUBLIC_ENTE_WIP_VIDEO_STREAMING + ) { + if (file.metadata.fileType == FileType.video) { + void hlsPlaylistForFile(file); + } + } + const annotation: FileViewerFileAnnotation = { fileID, isOwnFile, @@ -586,35 +580,31 @@ export const FileViewer: React.FC = ({ const isFavorite = useCallback( ({ file }: FileViewerAnnotatedFile) => { - if (!haveUser || !favoriteFileIDs || !onToggleFavorite) { + if ( + !haveUser || + !favoriteFileIDs || + !pendingFavoriteUpdates || + !onToggleFavorite + ) { return undefined; } return favoriteFileIDs.has(file.id); }, - [haveUser, favoriteFileIDs, onToggleFavorite], + [haveUser, favoriteFileIDs, pendingFavoriteUpdates, onToggleFavorite], + ); + + const isFavoritePending = useCallback( + ({ file }: FileViewerAnnotatedFile) => + !!pendingFavoriteUpdates?.has(file.id), + [pendingFavoriteUpdates], ); const toggleFavorite = useCallback( - ({ file }: FileViewerAnnotatedFile) => { - return new Promise((resolve) => { - // See: [Note: File viewer update and dispatch] - onToggleFavorite!(file) - .then( - () => awaitNextFilesOrFavoritesUpdate(() => resolve()), - (e: unknown) => { - onGenericError(e); - resolve(); - }, - ) - .finally(handleNeedsRemoteSync); - }); - }, - [ - onToggleFavorite, - onGenericError, - awaitNextFilesOrFavoritesUpdate, - handleNeedsRemoteSync, - ], + ({ file }: FileViewerAnnotatedFile) => + onToggleFavorite!(file) + .catch(onGenericError) + .finally(handleNeedsRemoteSync), + [onToggleFavorite, onGenericError, handleNeedsRemoteSync], ); const updateFullscreenStatus = useCallback(() => { @@ -761,6 +751,7 @@ export const FileViewer: React.FC = ({ delegateRef.current = { getFiles, isFavorite, + isFavoritePending, toggleFavorite, shouldIgnoreKeyboardEvent, performKeyAction, @@ -772,12 +763,14 @@ export const FileViewer: React.FC = ({ const delegate = delegateRef.current!; delegate.getFiles = getFiles; delegate.isFavorite = isFavorite; + delegate.isFavoritePending = isFavoritePending; delegate.toggleFavorite = toggleFavorite; delegate.shouldIgnoreKeyboardEvent = shouldIgnoreKeyboardEvent; delegate.performKeyAction = performKeyAction; }, [ getFiles, isFavorite, + isFavoritePending, toggleFavorite, shouldIgnoreKeyboardEvent, performKeyAction, @@ -831,13 +824,11 @@ export const FileViewer: React.FC = ({ return af; }); + }, [handleClose, files]); - // - Used for favorite updates. - setOnNextFilesOrFavoritesUpdate((cbs) => { - cbs.forEach((cb) => cb(files)); - return []; - }); - }, [handleClose, files, favoriteFileIDs]); + useEffect(() => { + psRef.current?.refreshCurrentSlideFavoriteButtonIfNeeded(); + }, [favoriteFileIDs, pendingFavoriteUpdates]); useEffect(() => { if (open) { @@ -904,12 +895,13 @@ export const FileViewer: React.FC = ({ exif={activeFileExif} allowEdits={!!activeAnnotatedFile.annotation.isOwnFile} allowMap={haveUser} - showCollections={haveUser} + showCollections={haveUser && !isInHiddenSection} + fileCollectionIDs={fileNormalCollectionIDs} + collectionNameByID={collectionNameByID} onNeedsRemoteSync={handleNeedsRemoteSync} onUpdateCaption={handleUpdateCaption} onSelectCollection={handleSelectCollection} onSelectPerson={handleSelectPerson} - {...{ fileCollectionIDs, allCollectionsNameByID }} /> boolean | undefined; + /** + * Return `true` if there is an inflight request to update the favorite + * status of the file. + */ + isFavoritePending: (annotatedFile: FileViewerAnnotatedFile) => boolean; /** * Called when the user activates the toggle favorite action on a file. * @@ -609,21 +614,19 @@ export class FileViewerPhotoSwipe { let favoriteButtonElement: HTMLButtonElement | undefined; - /** - * IDs of files for which a there is a favorite update in progress. - */ - const pendingFavoriteUpdates = new Set(); + const toggleFavorite = () => + delegate.toggleFavorite(currentAnnotatedFile()); - const toggleFavorite = async () => { - const af = currentAnnotatedFile(); - pendingFavoriteUpdates.add(af.file.id); - updateFavoriteButton(); - await delegate.toggleFavorite(af); - pendingFavoriteUpdates.delete(af.file.id); - updateFavoriteButton(); - }; + const updateFavoriteButtonIfNeeded = () => { + const favoriteIconFill = document.getElementById( + "pswp__icn-favorite-fill", + ); + if (!favoriteIconFill) { + // Early return if we're not currently being shown, to implement + // the "IfNeeded" semantics. + return; + } - const updateFavoriteButton = () => { const button = favoriteButtonElement!; const af = currentAnnotatedFile(); @@ -636,20 +639,24 @@ export class FileViewerPhotoSwipe { } // Update the button interactivity based on pending requests. - button.disabled = pendingFavoriteUpdates.has(af.file.id); + button.disabled = delegate.isFavoritePending(af); // Update the fill visibility based on the favorite status. - const fill = document.getElementById("pswp__icn-favorite-fill"); - if (fill) { - // Need a null check since we might've been closed meanwhile. - showIf(fill, !!delegate.isFavorite(af)); - } + showIf(favoriteIconFill, !!delegate.isFavorite(af)); }; + this.refreshCurrentSlideFavoriteButtonIfNeeded = + updateFavoriteButtonIfNeeded; + const handleToggleFavorite = () => void toggleFavorite(); const handleToggleFavoriteIfEnabled = () => { - if (haveUser) handleToggleFavorite(); + if ( + haveUser && + !delegate.isFavoritePending(currentAnnotatedFile()) + ) { + handleToggleFavorite(); + } }; const handleDownload = () => onDownload(currentAnnotatedFile()); @@ -761,7 +768,7 @@ export class FileViewerPhotoSwipe { // The cast should be safe (unless there is a // PhotoSwipe bug) since we set isButton to true. buttonElement as HTMLButtonElement; - pswp.on("change", updateFavoriteButton); + pswp.on("change", updateFavoriteButtonIfNeeded); }, onClick: handleToggleFavorite, }); @@ -1036,6 +1043,15 @@ export class FileViewerPhotoSwipe { this.pswp.refreshSlideContent(this.pswp.currIndex); } } + + /** + * Refresh the favorite button (if indeed it is visible at all) on the + * current slide, asking the delegate for the latest state. + * + * We do this piecemeal update instead of a full refresh because a full + * refresh would cause, e.g., the pan and zoom to be reset. + */ + refreshCurrentSlideFavoriteButtonIfNeeded: () => void; } const videoHTML = (url: string, disableDownload: boolean) => ` diff --git a/web/packages/new/photos/services/file-data.ts b/web/packages/gallery/services/file-data.ts similarity index 81% rename from web/packages/new/photos/services/file-data.ts rename to web/packages/gallery/services/file-data.ts index c7367ccbea..724db99a73 100644 --- a/web/packages/new/photos/services/file-data.ts +++ b/web/packages/gallery/services/file-data.ts @@ -18,7 +18,9 @@ import { z } from "zod"; * There are specialized APIs for fetching and uploading the originals and the * thumbnails. But for the other associated data, we can use the file data APIs. */ -type FileDataType = "mldata" /* See: [Note: "mldata" format] */; +type FileDataType = + | "mldata" /* See: [Note: "mldata" format] */ + | "vid_preview" /* See: [Note: Video playlist and preview] */; const RemoteFileData = z.object({ /** @@ -61,7 +63,7 @@ type RemoteFileData = z.infer; * payload, but we don't parse that information currently since the higher * levels of our code that use this function handle such rare skips gracefully. */ -export const fetchFileData = async ( +export const fetchFilesData = async ( type: FileDataType, fileIDs: number[], ): Promise => { @@ -75,6 +77,27 @@ export const fetchFileData = async ( .data; }; +/** + * A variant of {@link fetchFilesData} that fetches data for a single file. + * + * Unlike {@link fetchFilesData}, this uses a HTTP GET request. + * + * Returns `undefined` if no video preview has been generated for this file yet. + */ +export const fetchFileData = async ( + type: FileDataType, + fileID: number, +): Promise => { + const params = new URLSearchParams({ type, fileID: fileID.toString() }); + const url = await apiURL("/files/data/fetch"); + const res = await fetch(`${url}?${params.toString()}`, { + headers: await authenticatedRequestHeaders(), + }); + if (res.status == 404) return undefined; + ensureOk(res); + return z.object({ data: RemoteFileData }).parse(await res.json()).data; +}; + /** * Upload file data associated with the given file to remote. * diff --git a/web/packages/gallery/services/upload.ts b/web/packages/gallery/services/upload.ts index e6b1040d73..36e5096814 100644 --- a/web/packages/gallery/services/upload.ts +++ b/web/packages/gallery/services/upload.ts @@ -94,17 +94,16 @@ export type UploadPhase = | "cancelling" | "done"; -export enum UPLOAD_RESULT { - FAILED, - ALREADY_UPLOADED, - UNSUPPORTED, - BLOCKED, - TOO_LARGE, - LARGER_THAN_AVAILABLE_STORAGE, - UPLOADED, - UPLOADED_WITH_STATIC_THUMBNAIL, - ADDED_SYMLINK, -} +export type UploadResult = + | "failed" + | "alreadyUploaded" + | "unsupported" + | "blocked" + | "tooLarge" + | "largerThanAvailableStorage" + | "uploaded" + | "uploadedWithStaticThumbnail" + | "addedSymlink"; /** * Return true to disable the upload of files via Cloudflare Workers. diff --git a/web/packages/gallery/services/video.ts b/web/packages/gallery/services/video.ts new file mode 100644 index 0000000000..ff769f9a2a --- /dev/null +++ b/web/packages/gallery/services/video.ts @@ -0,0 +1,96 @@ +import { decryptBlob } from "@/base/crypto"; +import type { EncryptedBlob } from "@/base/crypto/types"; +import log from "@/base/log"; +import type { EnteFile } from "@/media/file"; +import { FileType } from "@/media/file-type"; +import { gunzip } from "@/new/photos/utils/gzip"; +import { ensurePrecondition } from "@/utils/ensure"; +import { z } from "zod"; +import { fetchFileData } from "./file-data"; + +/** + * Return a HLS playlist that can be used to stream playback of thne given video + * {@link file}. + * + * @param file An {@link EnteFile} of type video. + * + * @returns The HLS playlist as a string, or `undefined` if there is no video + * preview associated with the given file. + * + * [Note: Video playlist and preview] + * + * In museum's ontology, there is a distinction between two concepts: + * + * S3 metadata is the data that museum uploads (on behalf of the client): + * - ML data. + * - Preview video playlist. + * + * S3 file data is the data that client uploads: + * - Preview video itself. + * - Additional preview images. + * + * Because of this separation, there are separate code paths dealing with the + * two parts we need to play streaming video: + * + * - The encrypted HLS playlist (which is stored as file data of type + * "vid_preview"), + * + * - And the encrypted video chunks that it (the playlist) refers to (which are + * stored as file preview data of type "vid_preview"). + */ +export const hlsPlaylistForFile = async (file: EnteFile) => { + ensurePrecondition(file.metadata.fileType == FileType.video); + + const playlistFileData = await fetchFileData("vid_preview", file.id); + if (!playlistFileData) return undefined; + + // See: [Note: strict mode migration] + // + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const { playlist } = await decryptPlaylistJSON(playlistFileData, file); + + // [Note: HLS playlist format] + // + // The decrypted playlist is a regular HLS playlist for an encrypted media + // stream, except that it uses a placeholder "output.ts" which needs to be + // replaced with the URL of the actual encrypted video data. A single URL + // pointing to the entire encrypted video data suffices; the individual + // chunks are fetched by HTTP range requests. + // + // Here is an example of what the contents of the `playlist` variable might + // look like at this point: + // + // #EXTM3U + // #EXT-X-VERSION:4 + // #EXT-X-TARGETDURATION:8 + // #EXT-X-MEDIA-SEQUENCE:0 + // #EXT-X-KEY:METHOD=AES-128,URI="data:text/plain;base64,XjvG7qeRrsOpPUbJPh2Ikg==",IV=0x00000000000000000000000000000000 + // #EXTINF:8.333333, + // #EXT-X-BYTERANGE:3046928@0 + // output.ts + // #EXTINF:8.333333, + // #EXT-X-BYTERANGE:3012704@3046928 + // output.ts + // #EXTINF:2.200000, + // #EXT-X-BYTERANGE:834736@6059632 + // output.ts + // #EXT-X-ENDLIST + // + log.debug(() => ["hlsPlaylistForFile", playlist]); + return file.id; +}; + +const PlaylistJSON = z.object({ + /** The HLS playlist, as a string. */ + playlist: z.string(), +}); + +const decryptPlaylistJSON = async ( + encryptedPlaylist: EncryptedBlob, + file: EnteFile, +) => { + const decryptedBytes = await decryptBlob(encryptedPlaylist, file.key); + const jsonString = await gunzip(decryptedBytes); + return PlaylistJSON.parse(JSON.parse(jsonString)); +}; diff --git a/web/packages/media/collection.ts b/web/packages/media/collection.ts index a813afaed3..145789f9a2 100644 --- a/web/packages/media/collection.ts +++ b/web/packages/media/collection.ts @@ -6,24 +6,14 @@ import { ItemVisibility } from "@/media/file-metadata"; // TODO: Audit this file -export enum CollectionType { - folder = "folder", - favorites = "favorites", - album = "album", - uncategorized = "uncategorized", -} +export type CollectionType = "folder" | "favorites" | "album" | "uncategorized"; -export enum COLLECTION_ROLE { - VIEWER = "VIEWER", - OWNER = "OWNER", - COLLABORATOR = "COLLABORATOR", - UNKNOWN = "UNKNOWN", -} +export type CollectionRole = "VIEWER" | "OWNER" | "COLLABORATOR" | "UNKNOWN"; export interface CollectionUser { id: number; email: string; - role: COLLECTION_ROLE; + role: CollectionRole; } export interface EncryptedCollection { @@ -112,15 +102,18 @@ export interface RemoveFromCollectionRequest { fileIDs: number[]; } -export enum SUB_TYPE { - DEFAULT = 0, - DEFAULT_HIDDEN = 1, - QUICK_LINK_COLLECTION = 2, -} +export const CollectionSubType = { + default: 0, + defaultHidden: 1, + quicklink: 2, +} as const; + +export type CollectionSubType = + (typeof CollectionSubType)[keyof typeof CollectionSubType]; export interface CollectionMagicMetadataProps { visibility?: ItemVisibility; - subType?: SUB_TYPE; + subType?: CollectionSubType; order?: number; } diff --git a/web/packages/media/file-metadata.ts b/web/packages/media/file-metadata.ts index 20e3da1eda..1b5b1f412a 100644 --- a/web/packages/media/file-metadata.ts +++ b/web/packages/media/file-metadata.ts @@ -175,14 +175,23 @@ export interface PrivateMagicMetadata { /** * The visibility of an Ente file or collection. */ -export enum ItemVisibility { +export const ItemVisibility = { /** The normal state - The item is visible. */ - visible = 0, + visible: 0, /** The item has been archived. */ - archived = 1, + archived: 1, /** The item has been hidden. */ - hidden = 2, -} + hidden: 2, +} as const; + +/** + * The visibility of an Ente file or collection. + * + * This is the erasable type. See the {@link ItemVisibility} object for the + * possible values and their symbolic constants. + */ +export type ItemVisibility = + (typeof ItemVisibility)[keyof typeof ItemVisibility]; /** * Mutable public metadata associated with an {@link EnteFile}. diff --git a/web/packages/media/file-type.ts b/web/packages/media/file-type.ts index bb8ceeede3..e8dcf6b2c9 100644 --- a/web/packages/media/file-type.ts +++ b/web/packages/media/file-type.ts @@ -1,9 +1,18 @@ -/** The type of an {@link EnteFile}. */ -export enum FileType { - /** An image (e.g. JPEG). */ - image = 0, - /** A video (e.g. MP4). */ - video = 1, +/** + * The type of an {@link EnteFile}. + * + * This is an object containing symbolic constant. There is also an eraseable + * TypeScript type type with the same name, {@link FileType}. + */ +export const FileType = { + /** + * An image (e.g. JPEG). + */ + image: 0, + /** + * A video (e.g. MP4). + */ + video: 1, /** * A live photo, aka motion photo. * @@ -11,7 +20,7 @@ export enum FileType { * before and after when the image was taken. We preserve it as a zip * containing both the parts. */ - livePhoto = 2, + livePhoto: 2, /** * An unknown file type. * @@ -20,8 +29,16 @@ export enum FileType { * deal with the scenario that an EnteFile's type can be different from one * of the above. */ - other = 3, -} + other: 3, +} as const; + +/** + * The type of an {@link EnteFile}. + * + * This is an eraseable TypeScript type. There is also a {@link FileType} object + * with the same name that contains the corresponding symbolic constants. + */ +export type FileType = (typeof FileType)[keyof typeof FileType]; export interface FileTypeInfo { fileType: FileType; diff --git a/web/packages/new/photos/components/FileDateTimePicker.tsx b/web/packages/new/photos/components/FileDateTimePicker.tsx index a3e4a3e0e2..d90df26083 100644 --- a/web/packages/new/photos/components/FileDateTimePicker.tsx +++ b/web/packages/new/photos/components/FileDateTimePicker.tsx @@ -57,7 +57,6 @@ export const FileDateTimePicker: React.FC = ({ }; const handleClose = () => { - console.log("handleClose"); setOpen(false); onDidClose?.(); }; diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 6a4bf0732d..dc5d82e253 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -2,11 +2,7 @@ import { isArchivedCollection, isPinnedCollection, } from "@/gallery/services/magic-metadata"; -import { - COLLECTION_ROLE, - CollectionType, - type Collection, -} from "@/media/collection"; +import { type Collection } from "@/media/collection"; import type { EnteFile, FilePrivateMagicMetadata } from "@/media/file"; import { mergeMetadata } from "@/media/file"; import { isArchivedFile } from "@/media/file-metadata"; @@ -124,7 +120,7 @@ export interface GalleryState { /** * The user's normal (non-hidden) collections. */ - collections: Collection[]; + normalCollections: Collection[]; /** * The user's hidden collections. */ @@ -136,9 +132,9 @@ export interface GalleryState { * The list is sorted so that newer files are first. * * This property is expected to be of use only internal to the reducer; - * external code likely needs {@link files} instead. + * external code likely needs {@link normalFiles} instead. */ - lastSyncedFiles: EnteFile[]; + lastSyncedNormalFiles: EnteFile[]; /** * The user's hidden files, without any unsynced modifications applied to * them. @@ -150,7 +146,7 @@ export interface GalleryState { */ lastSyncedHiddenFiles: EnteFile[]; /** - * The user's files that are in Trash. + * The user's files that are in trash. * * The list is sorted so that newer files are first. */ @@ -177,7 +173,7 @@ export interface GalleryState { * happen on the next "file sync", until then they remain as in-memory state * in the reducer. */ - files: EnteFile[]; + normalFiles: EnteFile[]; /** * The user's hidden files, with any unsynced modifications also applied to * them. @@ -226,6 +222,8 @@ export interface GalleryState { * File IDs of all the files that the user has marked as a favorite. * * Includes the effects of {@link unsyncedFavoriteUpdates}. + * + * For fast lookup, this is a set instead of a list. */ favoriteFileIDs: Set; /** @@ -233,21 +231,23 @@ export interface GalleryState { * * It will contain entries for all collections (both normal and hidden). */ - allCollectionsNameByID: Map; + collectionNameByID: Map; /** - * A map from file IDs to the IDs of the collections that they're a part of. + * A map from file IDs to the IDs of the normal (non-hidden) collections + * that they're a part of. */ - fileCollectionIDs: Map; + fileNormalCollectionIDs: Map; /*--< Derived UI state >--*/ /** - * A map of collections massage to a form suitable for being directly - * consumed by the UI, indexed by the collection IDs. + * A map of normal (non-hidden) collections massaged into a form suitable + * for being directly consumed by the UI, indexed by the collection IDs. */ - collectionSummaries: Map; + normalCollectionSummaries: Map; /** - * A version of {@link collectionSummaries} but for hidden collections. + * A variant of {@link normalCollectionSummaries}, but for hidden + * collections. */ hiddenCollectionSummaries: Map; @@ -267,9 +267,30 @@ export interface GalleryState { * hidden. */ tempHiddenFileIDs: Set; + /** + * File (IDs) for which there is currently an in-flight favorite / + * unfavorite operation. + * + * See also {@link unsyncedFavoriteUpdates}. + */ + pendingFavoriteUpdates: Set; + /** + * Updates to the favorite status of files (triggered by some interactive + * user action) that have already been made to applied to remote, but whose + * effects on remote have not yet been synced back to our local DB. + * + * Each entry from a file ID to its favorite status (`true` if it belongs to + * the user's favorites, false otherwise) which should be used for that file + * instead of what we get from our local DB. + * + * The next time a sync with remote completes, we clear this map since + * thereafter just deriving {@link favoriteFileIDs} from our local files + * would reflect the correct state on remote too. + */ + unsyncedFavoriteUpdates: Map; /** * File (IDs) for which there is currently an in-flight archive / unarchive - * operation trigged via the file viewer. + * operation. * * See also {@link unsyncedPrivateMagicMetadataUpdates}. */ @@ -287,32 +308,6 @@ export interface GalleryState { * magic metadata. */ unsyncedPrivateMagicMetadataUpdates: Map; - /** - * Transient updates to the favorite status of files that have just been - * toggled by the user in the file viewer, and on remote, but whose effects - * on remote have not yet been synced back to our local DB. - * - * Each entry is from a file ID to `true` (if that file should be considered - * as part of the favorites) or `false` (if that file should not be - * considered as part of the favorites). - * - * When the user marks a file as a favorite (or unmarks it as a favorite), - * we add an entry in this map so that we can give them immediate feedback - * in the UI. - * - * The request to update the favorite status on remote proceeds in parallel. - * If that request fails, we remove the entry from here. - * - * If the remote request succeeds, we still need to sync the files and - * collections in our local DB with the remote state, but that happens in a - * batch when the user exits the viewer. So until that point, these updates - * remain in this in-flight updates map. - * - * Once the remote file + collection sync completes, we can clear this map - * since just deriving {@link favoriteFileIDs} from our local files would - * reflect the correct state on remote too. - */ - unsyncedFavoriteUpdates: Map; /*--< State that underlies transient UI state >--*/ @@ -412,28 +407,32 @@ export type GalleryAction = type: "mount"; user: User; familyData: FamilyData; - allCollections: Collection[]; - files: EnteFile[]; + collections: Collection[]; + normalFiles: EnteFile[]; hiddenFiles: EnteFile[]; trashedFiles: EnteFile[]; } - | { type: "setNormalCollections"; collections: Collection[] } | { type: "setCollections"; + collections: Collection[]; normalCollections: Collection[]; hiddenCollections: Collection[]; } - | { type: "setFiles"; files: EnteFile[] } - | { type: "fetchFiles"; files: EnteFile[] } - | { type: "uploadFile"; file: EnteFile } - | { type: "setHiddenFiles"; hiddenFiles: EnteFile[] } - | { type: "fetchHiddenFiles"; hiddenFiles: EnteFile[] } - | { type: "setTrashedFiles"; trashedFiles: EnteFile[] } + | { type: "setNormalCollections"; collections: Collection[] } + | { type: "setNormalFiles"; files: EnteFile[] } + | { type: "fetchNormalFiles"; files: EnteFile[] } + | { type: "uploadNormalFile"; file: EnteFile } + | { type: "setHiddenFiles"; files: EnteFile[] } + | { type: "fetchHiddenFiles"; files: EnteFile[] } + | { type: "setTrashedFiles"; files: EnteFile[] } | { type: "setPeopleState"; peopleState: PeopleState | undefined } | { type: "markTempDeleted"; files: EnteFile[] } | { type: "clearTempDeleted" } | { type: "markTempHidden"; files: EnteFile[] } | { type: "clearTempHidden" } + | { type: "addPendingFavoriteUpdate"; fileID: number } + | { type: "removePendingFavoriteUpdate"; fileID: number } + | { type: "unsyncedFavoriteUpdate"; fileID: number; isFavorite: boolean } | { type: "addPendingVisibilityUpdate"; fileID: number } | { type: "removePendingVisibilityUpdate"; fileID: number } | { @@ -441,21 +440,11 @@ export type GalleryAction = fileID: number; privateMagicMetadata: FilePrivateMagicMetadata; } - | { - type: "markUnsyncedFavoriteUpdate"; - fileID: number; - // Passing undefined clears any existing entry, concrete values add or - // update one. - isFavorite: boolean | undefined; - } | { type: "clearUnsyncedState" } | { type: "showAll" } | { type: "showHidden" } | { type: "showAlbums" } - | { - type: "showNormalOrHiddenCollectionSummary"; - collectionSummaryID: number | undefined; - } + | { type: "showCollectionSummary"; collectionSummaryID: number | undefined } | { type: "showPeople" } | { type: "showPerson"; personID: string } | { type: "enterSearchMode"; searchSuggestion?: SearchSuggestion } @@ -466,28 +455,29 @@ export type GalleryAction = const initialGalleryState: GalleryState = { user: undefined, familyData: undefined, - collections: [], + normalCollections: [], hiddenCollections: [], - lastSyncedFiles: [], + lastSyncedNormalFiles: [], lastSyncedHiddenFiles: [], trashedFiles: [], peopleState: undefined, - files: [], + normalFiles: [], hiddenFiles: [], archivedCollectionIDs: new Set(), defaultHiddenCollectionIDs: new Set(), hiddenFileIDs: new Set(), archivedFileIDs: new Set(), favoriteFileIDs: new Set(), - allCollectionsNameByID: new Map(), - fileCollectionIDs: new Map(), - collectionSummaries: new Map(), + collectionNameByID: new Map(), + fileNormalCollectionIDs: new Map(), + normalCollectionSummaries: new Map(), hiddenCollectionSummaries: new Map(), tempDeletedFileIDs: new Set(), tempHiddenFileIDs: new Set(), + pendingFavoriteUpdates: new Set(), + unsyncedFavoriteUpdates: new Map(), pendingVisibilityUpdates: new Set(), unsyncedPrivateMagicMetadataUpdates: new Map(), - unsyncedFavoriteUpdates: new Map(), selectedCollectionSummaryID: undefined, selectedPersonID: undefined, extraVisiblePerson: undefined, @@ -507,42 +497,45 @@ const galleryReducer: React.Reducer = ( if (process.env.NEXT_PUBLIC_ENTE_TRACE) console.log("dispatch", action); switch (action.type) { case "mount": { - const lastSyncedFiles = sortFiles(mergeMetadata(action.files)); + const lastSyncedNormalFiles = sortFiles( + mergeMetadata(action.normalFiles), + ); const lastSyncedHiddenFiles = sortFiles( mergeMetadata(action.hiddenFiles), ); // During mount there are no unsynced updates, and we can directly // use the provided files. - const files = lastSyncedFiles; + const normalFiles = lastSyncedNormalFiles; const hiddenFiles = lastSyncedHiddenFiles; - const [hiddenCollections, collections] = splitByPredicate( - action.allCollections, + const [hiddenCollections, normalCollections] = splitByPredicate( + action.collections, isHiddenCollection, ); const hiddenFileIDs = deriveHiddenFileIDs(hiddenFiles); const archivedCollectionIDs = - deriveArchivedCollectionIDs(collections); + deriveArchivedCollectionIDs(normalCollections); const archivedFileIDs = deriveArchivedFileIDs( archivedCollectionIDs, - files, + normalFiles, ); const view = { type: "albums" as const, activeCollectionSummaryID: ALL_SECTION, activeCollection: undefined, }; + return stateByUpdatingFilteredFiles({ ...state, user: action.user, familyData: action.familyData, - collections, + normalCollections, hiddenCollections, - lastSyncedFiles, + lastSyncedNormalFiles, lastSyncedHiddenFiles, trashedFiles: action.trashedFiles, - files, + normalFiles, hiddenFiles, archivedCollectionIDs, defaultHiddenCollectionIDs: @@ -550,18 +543,18 @@ const galleryReducer: React.Reducer = ( hiddenFileIDs, archivedFileIDs, favoriteFileIDs: deriveFavoriteFileIDs( - collections, - files, + normalCollections, + normalFiles, state.unsyncedFavoriteUpdates, ), - allCollectionsNameByID: createCollectionNameByID( - action.allCollections, + collectionNameByID: createCollectionNameByID( + action.collections, ), - fileCollectionIDs: createFileCollectionIDs(files), - collectionSummaries: deriveCollectionSummaries( + fileNormalCollectionIDs: createFileCollectionIDs(normalFiles), + normalCollectionSummaries: deriveNormalCollectionSummaries( action.user, - collections, - files, + normalCollections, + normalFiles, action.trashedFiles, archivedFileIDs, ), @@ -574,71 +567,19 @@ const galleryReducer: React.Reducer = ( }); } - case "setNormalCollections": { - const collections = action.collections; - const archivedCollectionIDs = - deriveArchivedCollectionIDs(collections); - const archivedFileIDs = deriveArchivedFileIDs( - archivedCollectionIDs, - state.files, - ); - const collectionSummaries = deriveCollectionSummaries( - state.user!, - collections, - state.files, - state.trashedFiles, - archivedFileIDs, - ); - - // Revalidate the active view if needed. - let view = state.view; - let selectedCollectionSummaryID = state.selectedCollectionSummaryID; - if (state.view?.type == "albums") { - ({ view, selectedCollectionSummaryID } = - deriveAlbumsViewAndSelectedID( - collections, - collectionSummaries, - selectedCollectionSummaryID, - )); - } - - return stateByUpdatingFilteredFiles({ - ...state, - collections, - archivedCollectionIDs, - archivedFileIDs, - favoriteFileIDs: deriveFavoriteFileIDs( - collections, - state.files, - state.unsyncedFavoriteUpdates, - ), - allCollectionsNameByID: createCollectionNameByID( - collections.concat(state.hiddenCollections), - ), - collectionSummaries, - selectedCollectionSummaryID, - pendingSearchSuggestions: - enqueuePendingSearchSuggestionsIfNeeded( - state.searchSuggestion, - state.pendingSearchSuggestions, - state.isInSearchMode, - ), - view, - }); - } - case "setCollections": { - const { normalCollections, hiddenCollections } = action; + const { collections, normalCollections, hiddenCollections } = + action; const archivedCollectionIDs = deriveArchivedCollectionIDs(normalCollections); const archivedFileIDs = deriveArchivedFileIDs( archivedCollectionIDs, - state.files, + state.normalFiles, ); - const collectionSummaries = deriveCollectionSummaries( + const normalCollectionSummaries = deriveNormalCollectionSummaries( state.user!, normalCollections, - state.files, + state.normalFiles, state.trashedFiles, archivedFileIDs, ); @@ -655,7 +596,7 @@ const galleryReducer: React.Reducer = ( ({ view, selectedCollectionSummaryID } = deriveAlbumsViewAndSelectedID( normalCollections, - collectionSummaries, + normalCollectionSummaries, selectedCollectionSummaryID, )); } else if (state.view?.type == "hidden-albums") { @@ -669,7 +610,7 @@ const galleryReducer: React.Reducer = ( return stateByUpdatingFilteredFiles({ ...state, - collections: normalCollections, + normalCollections, hiddenCollections, archivedCollectionIDs, defaultHiddenCollectionIDs: @@ -677,13 +618,11 @@ const galleryReducer: React.Reducer = ( archivedFileIDs, favoriteFileIDs: deriveFavoriteFileIDs( normalCollections, - state.files, + state.normalFiles, state.unsyncedFavoriteUpdates, ), - allCollectionsNameByID: createCollectionNameByID( - normalCollections.concat(hiddenCollections), - ), - collectionSummaries, + collectionNameByID: createCollectionNameByID(collections), + normalCollectionSummaries, hiddenCollectionSummaries, selectedCollectionSummaryID, pendingSearchSuggestions: @@ -696,68 +635,121 @@ const galleryReducer: React.Reducer = ( }); } - case "setFiles": { + case "setNormalCollections": { + const normalCollections = action.collections; + const archivedCollectionIDs = + deriveArchivedCollectionIDs(normalCollections); + const archivedFileIDs = deriveArchivedFileIDs( + archivedCollectionIDs, + state.normalFiles, + ); + const normalCollectionSummaries = deriveNormalCollectionSummaries( + state.user!, + normalCollections, + state.normalFiles, + state.trashedFiles, + archivedFileIDs, + ); + + // Revalidate the active view if needed. + let view = state.view; + let selectedCollectionSummaryID = state.selectedCollectionSummaryID; + if (state.view?.type == "albums") { + ({ view, selectedCollectionSummaryID } = + deriveAlbumsViewAndSelectedID( + normalCollections, + normalCollectionSummaries, + selectedCollectionSummaryID, + )); + } + + return stateByUpdatingFilteredFiles({ + ...state, + normalCollections, + archivedCollectionIDs, + archivedFileIDs, + favoriteFileIDs: deriveFavoriteFileIDs( + normalCollections, + state.normalFiles, + state.unsyncedFavoriteUpdates, + ), + collectionNameByID: createCollectionNameByID( + normalCollections.concat(state.hiddenCollections), + ), + normalCollectionSummaries, + selectedCollectionSummaryID, + pendingSearchSuggestions: + enqueuePendingSearchSuggestionsIfNeeded( + state.searchSuggestion, + state.pendingSearchSuggestions, + state.isInSearchMode, + ), + view, + }); + } + + case "setNormalFiles": { const unsyncedPrivateMagicMetadataUpdates = prunedUnsyncedPrivateMagicMetadataUpdates( state.unsyncedPrivateMagicMetadataUpdates, action.files, ); - - const lastSyncedFiles = sortFiles(mergeMetadata(action.files)); - const files = deriveNormalOrHiddenFiles( - lastSyncedFiles, + const lastSyncedNormalFiles = sortFiles( + mergeMetadata(action.files), + ); + const normalFiles = deriveFiles( + lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, ); return stateByUpdatingFilteredFiles({ - ...stateForUpdatedFiles(state, files), - lastSyncedFiles, + ...stateForUpdatedNormalFiles(state, normalFiles), + lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, }); } - case "fetchFiles": { + case "fetchNormalFiles": { const unsyncedPrivateMagicMetadataUpdates = prunedUnsyncedPrivateMagicMetadataUpdates( state.unsyncedPrivateMagicMetadataUpdates, action.files, ); - - const lastSyncedFiles = sortFiles( + const lastSyncedNormalFiles = sortFiles( mergeMetadata( getLatestVersionFiles( - state.lastSyncedFiles.concat(action.files), + state.lastSyncedNormalFiles.concat(action.files), ), ), ); - const files = deriveNormalOrHiddenFiles( - lastSyncedFiles, + const normalFiles = deriveFiles( + lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, ); return stateByUpdatingFilteredFiles({ - ...stateForUpdatedFiles(state, files), - lastSyncedFiles, + ...stateForUpdatedNormalFiles(state, normalFiles), + lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, }); } - case "uploadFile": { + case "uploadNormalFile": { // TODO: Consider batching this instead of doing it per file // upload to speed up uploads. Perf test first though. - const lastSyncedFiles = sortFiles([ - ...state.lastSyncedFiles, + const lastSyncedNormalFiles = sortFiles([ + ...state.lastSyncedNormalFiles, action.file, ]); - const files = deriveNormalOrHiddenFiles( - lastSyncedFiles, + const normalFiles = deriveFiles( + lastSyncedNormalFiles, state.unsyncedPrivateMagicMetadataUpdates, ); return stateByUpdatingFilteredFiles({ - ...stateForUpdatedFiles(state, files), - lastSyncedFiles, + ...stateForUpdatedNormalFiles(state, normalFiles), + lastSyncedNormalFiles, }); } @@ -765,13 +757,12 @@ const galleryReducer: React.Reducer = ( const unsyncedPrivateMagicMetadataUpdates = prunedUnsyncedPrivateMagicMetadataUpdates( state.unsyncedPrivateMagicMetadataUpdates, - action.hiddenFiles, + action.files, ); - const lastSyncedHiddenFiles = sortFiles( - mergeMetadata(action.hiddenFiles), + mergeMetadata(action.files), ); - const hiddenFiles = deriveNormalOrHiddenFiles( + const hiddenFiles = deriveFiles( lastSyncedHiddenFiles, unsyncedPrivateMagicMetadataUpdates, ); @@ -787,17 +778,16 @@ const galleryReducer: React.Reducer = ( const unsyncedPrivateMagicMetadataUpdates = prunedUnsyncedPrivateMagicMetadataUpdates( state.unsyncedPrivateMagicMetadataUpdates, - action.hiddenFiles, + action.files, ); - const lastSyncedHiddenFiles = sortFiles( mergeMetadata( getLatestVersionFiles( - state.lastSyncedHiddenFiles.concat(action.hiddenFiles), + state.lastSyncedHiddenFiles.concat(action.files), ), ), ); - const hiddenFiles = deriveNormalOrHiddenFiles( + const hiddenFiles = deriveFiles( lastSyncedHiddenFiles, unsyncedPrivateMagicMetadataUpdates, ); @@ -812,12 +802,12 @@ const galleryReducer: React.Reducer = ( case "setTrashedFiles": return stateByUpdatingFilteredFiles({ ...state, - trashedFiles: action.trashedFiles, - collectionSummaries: deriveCollectionSummaries( + trashedFiles: action.files, + normalCollectionSummaries: deriveNormalCollectionSummaries( state.user!, - state.collections, - state.files, - action.trashedFiles, + state.normalCollections, + state.normalFiles, + action.files, state.archivedFileIDs, ), }); @@ -875,6 +865,41 @@ const galleryReducer: React.Reducer = ( tempHiddenFileIDs: new Set(), }); + case "addPendingFavoriteUpdate": { + const pendingFavoriteUpdates = new Set( + state.pendingFavoriteUpdates, + ); + pendingFavoriteUpdates.add(action.fileID); + return { ...state, pendingFavoriteUpdates }; + } + + case "removePendingFavoriteUpdate": { + const pendingFavoriteUpdates = new Set( + state.pendingFavoriteUpdates, + ); + pendingFavoriteUpdates.delete(action.fileID); + return { ...state, pendingFavoriteUpdates }; + } + + case "unsyncedFavoriteUpdate": { + const unsyncedFavoriteUpdates = new Map( + state.unsyncedFavoriteUpdates, + ); + unsyncedFavoriteUpdates.set(action.fileID, action.isFavorite); + + // Skipping a call to stateByUpdatingFilteredFiles since it + // currently doesn't depend on favorites. + return { + ...state, + favoriteFileIDs: deriveFavoriteFileIDs( + state.normalCollections, + state.normalFiles, + unsyncedFavoriteUpdates, + ), + unsyncedFavoriteUpdates, + }; + } + case "addPendingVisibilityUpdate": { const pendingVisibilityUpdates = new Set( state.pendingVisibilityUpdates, @@ -901,63 +926,51 @@ const galleryReducer: React.Reducer = ( action.fileID, action.privateMagicMetadata, ); - const files = deriveNormalOrHiddenFiles( - state.lastSyncedFiles, + const normalFiles = deriveFiles( + state.lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, ); + return stateByUpdatingFilteredFiles({ - ...stateForUpdatedFiles(state, files), + ...stateForUpdatedNormalFiles(state, normalFiles), unsyncedPrivateMagicMetadataUpdates, }); } - case "markUnsyncedFavoriteUpdate": { - const unsyncedFavoriteUpdates = new Map( - state.unsyncedFavoriteUpdates, - ); - if (action.isFavorite === undefined) { - unsyncedFavoriteUpdates.delete(action.fileID); - } else { - unsyncedFavoriteUpdates.set(action.fileID, action.isFavorite); - } - // Skipping a call to stateByUpdatingFilteredFiles since it - // currently doesn't depend on favorites. - return { - ...state, - favoriteFileIDs: deriveFavoriteFileIDs( - state.collections, - state.files, - unsyncedFavoriteUpdates, - ), - unsyncedFavoriteUpdates, - }; - } - case "clearUnsyncedState": { + const unsyncedFavoriteUpdates: GalleryState["unsyncedFavoriteUpdates"] = + new Map(); + const favoriteFileIDs = deriveFavoriteFileIDs( + state.normalCollections, + state.normalFiles, + unsyncedFavoriteUpdates, + ); + const unsyncedPrivateMagicMetadataUpdates: GalleryState["unsyncedPrivateMagicMetadataUpdates"] = new Map(); - - const files = deriveNormalOrHiddenFiles( - state.lastSyncedFiles, + const normalFiles = deriveFiles( + state.lastSyncedNormalFiles, unsyncedPrivateMagicMetadataUpdates, ); - const hiddenFiles = deriveNormalOrHiddenFiles( + const hiddenFiles = deriveFiles( state.lastSyncedHiddenFiles, unsyncedPrivateMagicMetadataUpdates, ); return stateByUpdatingFilteredFiles( stateForUpdatedHiddenFiles( - stateForUpdatedFiles( + stateForUpdatedNormalFiles( { ...state, + favoriteFileIDs, tempDeletedFileIDs: new Set(), tempHiddenFileIDs: new Set(), + pendingFavoriteUpdates: new Set(), pendingVisibilityUpdates: new Set(), unsyncedPrivateMagicMetadataUpdates, unsyncedFavoriteUpdates: new Map(), }, - files, + normalFiles, ), hiddenFiles, ), @@ -1001,10 +1014,11 @@ const galleryReducer: React.Reducer = ( case "showAlbums": { const { view, selectedCollectionSummaryID } = deriveAlbumsViewAndSelectedID( - state.collections, - state.collectionSummaries, + state.normalCollections, + state.normalCollectionSummaries, state.selectedCollectionSummaryID, ); + return stateByUpdatingFilteredFiles({ ...state, selectedCollectionSummaryID, @@ -1018,7 +1032,7 @@ const galleryReducer: React.Reducer = ( }); } - case "showNormalOrHiddenCollectionSummary": + case "showCollectionSummary": return stateByUpdatingFilteredFiles({ ...state, selectedCollectionSummaryID: action.collectionSummaryID, @@ -1037,7 +1051,7 @@ const galleryReducer: React.Reducer = ( : "albums", activeCollectionSummaryID: action.collectionSummaryID ?? ALL_SECTION, - activeCollection: state.collections + activeCollection: state.normalCollections .concat(state.hiddenCollections) .find(({ id }) => id === action.collectionSummaryID), }, @@ -1135,7 +1149,7 @@ export const useGalleryReducer = () => * Compute the effective files that we should use by overlaying the files we * read from disk by any temporary unsynced updates. */ -const deriveNormalOrHiddenFiles = ( +const deriveFiles = ( files: EnteFile[], unsyncedPrivateMagicMetadataUpdates: GalleryState["unsyncedPrivateMagicMetadataUpdates"], ) => { @@ -1181,7 +1195,7 @@ const deriveHiddenFileIDs = (hiddenFiles: EnteFile[]) => */ const deriveArchivedFileIDs = ( archivedCollectionIDs: GalleryState["archivedCollectionIDs"], - files: GalleryState["files"], + files: GalleryState["normalFiles"], ) => new Set( files @@ -1203,7 +1217,7 @@ const deriveFavoriteFileIDs = ( ) => { let favoriteFileIDs = new Set(); for (const collection of collections) { - if (collection.type === CollectionType.favorites) { + if (collection.type == "favorites") { favoriteFileIDs = new Set( files .filter((file) => file.collectionID === collection.id) @@ -1220,26 +1234,26 @@ const deriveFavoriteFileIDs = ( }; /** - * Compute collection summaries from their dependencies. + * Compute normal (non-hidden) collection summaries from their dependencies. */ -const deriveCollectionSummaries = ( +const deriveNormalCollectionSummaries = ( user: User, - collections: Collection[], - files: EnteFile[], + normalCollections: Collection[], + normalFiles: EnteFile[], trashedFiles: EnteFile[], archivedFileIDs: Set, ) => { - const collectionSummaries = createCollectionSummaries( + const normalCollectionSummaries = createCollectionSummaries( user, - collections, - files, + normalCollections, + normalFiles, ); - const uncategorizedCollection = collections.find( - ({ type }) => type === CollectionType.uncategorized, + const uncategorizedCollection = normalCollections.find( + ({ type }) => type == "uncategorized", ); if (!uncategorizedCollection) { - collectionSummaries.set(DUMMY_UNCATEGORIZED_COLLECTION, { + normalCollectionSummaries.set(DUMMY_UNCATEGORIZED_COLLECTION, { ...pseudoCollectionOptionsForFiles([]), id: DUMMY_UNCATEGORIZED_COLLECTION, type: "uncategorized", @@ -1248,15 +1262,18 @@ const deriveCollectionSummaries = ( }); } - const allSectionFiles = findAllSectionVisibleFiles(files, archivedFileIDs); - collectionSummaries.set(ALL_SECTION, { + const allSectionFiles = findAllSectionVisibleFiles( + normalFiles, + archivedFileIDs, + ); + normalCollectionSummaries.set(ALL_SECTION, { ...pseudoCollectionOptionsForFiles(allSectionFiles), id: ALL_SECTION, type: "all", attributes: ["all"], name: t("section_all"), }); - collectionSummaries.set(TRASH_SECTION, { + normalCollectionSummaries.set(TRASH_SECTION, { ...pseudoCollectionOptionsForFiles(trashedFiles), id: TRASH_SECTION, name: t("section_trash"), @@ -1265,9 +1282,9 @@ const deriveCollectionSummaries = ( coverFile: undefined, }); const archivedFiles = uniqueFilesByID( - files.filter((file) => isArchivedFile(file)), + normalFiles.filter((file) => isArchivedFile(file)), ); - collectionSummaries.set(ARCHIVE_SECTION, { + normalCollectionSummaries.set(ARCHIVE_SECTION, { ...pseudoCollectionOptionsForFiles(archivedFiles), id: ARCHIVE_SECTION, name: t("section_archive"), @@ -1276,7 +1293,7 @@ const deriveCollectionSummaries = ( coverFile: undefined, }); - return collectionSummaries; + return normalCollectionSummaries; }; const pseudoCollectionOptionsForFiles = (files: EnteFile[]) => ({ @@ -1344,23 +1361,7 @@ const createCollectionSummaries = ( } else if (isPinnedCollection(collection)) { type = "pinned"; } else { - // Directly use the collection type - // TODO: The constants can be aligned once collection type goes from - // an enum to an union. - switch (collection.type) { - case CollectionType.folder: - type = "folder"; - break; - case CollectionType.favorites: - type = "favorites"; - break; - case CollectionType.album: - type = "album"; - break; - case CollectionType.uncategorized: - type = "uncategorized"; - break; - } + type = collection.type; } // This block of code duplicates the above. Such duplication is needed @@ -1388,20 +1389,9 @@ const createCollectionSummaries = ( if (isPinnedCollection(collection)) { attributes.push("pinned"); } - switch (collection.type) { - case CollectionType.folder: - attributes.push("folder"); - break; - case CollectionType.favorites: - attributes.push("favorites"); - break; - case CollectionType.album: - attributes.push("album"); - break; - case CollectionType.uncategorized: - attributes.push("uncategorized"); - break; - } + // TODO: Verify type before removing the null check. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (collection.type) attributes.push(collection.type); let name: string; if (type == "uncategorized") { @@ -1467,7 +1457,7 @@ const isIncomingCollabShare = (collection: Collection, user: User) => { // TODO: Need to audit the types // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition const sharee = collection.sharees?.find((sharee) => sharee.id === user.id); - return sharee?.role === COLLECTION_ROLE.COLLABORATOR; + return sharee?.role == "COLLABORATOR"; }; const isOutgoingShare = (collection: Collection, user: User) => @@ -1492,8 +1482,8 @@ const findAllSectionVisibleFiles = ( * changed. */ const deriveAlbumsViewAndSelectedID = ( - collections: GalleryState["collections"], - collectionSummaries: GalleryState["collectionSummaries"], + collections: GalleryState["normalCollections"], + collectionSummaries: GalleryState["normalCollectionSummaries"], selectedCollectionSummaryID: GalleryState["selectedCollectionSummaryID"], ) => { // Make sure that the last selected ID is still valid by searching for it. @@ -1631,30 +1621,34 @@ const derivePeopleView = ( /** * Return a new state from the given {@link state} by recomputing all properties - * that depend on file using the provided {@link files}. + * that depend on normal (non-hidden) files, using the provided + * {@link normalFiles}. * * Usually, we update state by manually dependency tracking on a fine grained * basis, but it results in a duplicate code when the files themselves change, * since they effect many things. This is a convenience function for updating - * everything that needs to change when the files themselves change. + * everything that needs to change when the normal files themselves change. */ -const stateForUpdatedFiles = ( +const stateForUpdatedNormalFiles = ( state: GalleryState, - files: GalleryState["files"], -) => ({ + normalFiles: GalleryState["normalFiles"], +): GalleryState => ({ ...state, - files, - archivedFileIDs: deriveArchivedFileIDs(state.archivedCollectionIDs, files), + normalFiles, + archivedFileIDs: deriveArchivedFileIDs( + state.archivedCollectionIDs, + normalFiles, + ), favoriteFileIDs: deriveFavoriteFileIDs( - state.collections, - files, + state.normalCollections, + normalFiles, state.unsyncedFavoriteUpdates, ), - fileCollectionIDs: createFileCollectionIDs(files), - collectionSummaries: deriveCollectionSummaries( + fileNormalCollectionIDs: createFileCollectionIDs(normalFiles), + normalCollectionSummaries: deriveNormalCollectionSummaries( state.user!, - state.collections, - files, + state.normalCollections, + normalFiles, state.trashedFiles, state.archivedFileIDs, ), @@ -1667,17 +1661,17 @@ const stateForUpdatedFiles = ( /** * Return a new state from the given {@link state} by recomputing all properties - * that depend on file using the provided {@link hiddenFiles}. + * that depend on hidden files, using the provided {@link hiddenFiles}. * * Usually, we update state by manually dependency tracking on a fine grained * basis, but it results in a duplicate code when the hidden files themselves * change, since they effect a few things. This is a convenience function for - * updating everything that needs to change when the hiddenFiles change. + * updating everything that needs to change when the hidden files change. */ const stateForUpdatedHiddenFiles = ( state: GalleryState, hiddenFiles: GalleryState["hiddenFiles"], -) => ({ +): GalleryState => ({ ...state, hiddenFiles, hiddenFileIDs: deriveHiddenFileIDs(hiddenFiles), @@ -1704,7 +1698,7 @@ const stateByUpdatingFilteredFiles = (state: GalleryState) => { return { ...state, filteredFiles }; } else if (state.view?.type == "albums") { const filteredFiles = deriveAlbumsFilteredFiles( - state.files, + state.normalFiles, state.trashedFiles, state.hiddenFileIDs, state.archivedCollectionIDs, @@ -1724,7 +1718,7 @@ const stateByUpdatingFilteredFiles = (state: GalleryState) => { return { ...state, filteredFiles }; } else if (state.view?.type == "people") { const filteredFiles = derivePeopleFilteredFiles( - state.files, + state.normalFiles, state.view, ); return { ...state, filteredFiles }; @@ -1738,7 +1732,7 @@ const stateByUpdatingFilteredFiles = (state: GalleryState) => { * the dependencies change. */ const deriveAlbumsFilteredFiles = ( - files: GalleryState["files"], + normalFiles: GalleryState["normalFiles"], trashedFiles: GalleryState["trashedFiles"], hiddenFileIDs: GalleryState["hiddenFileIDs"], archivedCollectionIDs: GalleryState["archivedCollectionIDs"], @@ -1753,11 +1747,11 @@ const deriveAlbumsFilteredFiles = ( if (activeCollectionSummaryID === TRASH_SECTION) { return uniqueFilesByID([ ...trashedFiles, - ...files.filter((file) => tempDeletedFileIDs.has(file.id)), + ...normalFiles.filter((file) => tempDeletedFileIDs.has(file.id)), ]); } - const filteredFiles = files.filter((file) => { + const filteredFiles = normalFiles.filter((file) => { if (tempDeletedFileIDs.has(file.id)) return false; if (hiddenFileIDs.has(file.id)) return false; if (tempHiddenFileIDs.has(file.id)) return false; @@ -1851,12 +1845,12 @@ const sortAndUniqueFilteredFiles = ( * the dependencies change. */ const derivePeopleFilteredFiles = ( - files: GalleryState["files"], + normalFiles: GalleryState["normalFiles"], view: Extract, ) => { const pfSet = new Set(view.activePerson?.fileIDs ?? []); return uniqueFilesByID( - files.filter(({ id }) => { + normalFiles.filter(({ id }) => { if (!pfSet.has(id)) return false; return true; }), diff --git a/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx b/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx index a1958d5460..9118b83b3b 100644 --- a/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx +++ b/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx @@ -12,8 +12,7 @@ import { } from "@/base/components/mui/SidebarDrawer"; import { useBaseContext } from "@/base/context"; import { disable2FA, get2FAStatus } from "@/new/photos/services/user"; -import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; -import { LS_KEYS, getData, setLSUser } from "@ente/shared/storage/localStorage"; +import { getData, setLSUser } from "@ente/shared/storage/localStorage"; import LockIcon from "@mui/icons-material/Lock"; import { Stack, Typography } from "@mui/material"; import { t } from "i18next"; @@ -29,7 +28,7 @@ export const TwoFactorSettings: React.FC< // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const isTwoFactorEnabled = // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - getData(LS_KEYS.USER).isTwoFactorEnabled ?? false; + getData("user").isTwoFactorEnabled ?? false; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument setIsTwoFactorEnabled(isTwoFactorEnabled); }, []); @@ -41,7 +40,7 @@ export const TwoFactorSettings: React.FC< setIsTwoFactorEnabled(isEnabled); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await setLSUser({ - ...getData(LS_KEYS.USER), + ...getData("user"), isTwoFactorEnabled: isEnabled, }); })(); @@ -83,7 +82,7 @@ const SetupDrawerContents: React.FC = ({ onRootClose }) => { const configure = () => { onRootClose(); - void router.push(PAGES.TWO_FACTOR_SETUP); + void router.push("/two-factor/setup"); }; return ( @@ -122,10 +121,7 @@ const ManageDrawerContents: React.FC = ({ onRootClose }) => { const disable = async () => { await disable2FA(); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - await setLSUser({ - ...getData(LS_KEYS.USER), - isTwoFactorEnabled: false, - }); + await setLSUser({ ...getData("user"), isTwoFactorEnabled: false }); onRootClose(); }; @@ -142,7 +138,7 @@ const ManageDrawerContents: React.FC = ({ onRootClose }) => { const reconfigure = async () => { onRootClose(); - await router.push(PAGES.TWO_FACTOR_SETUP); + await router.push("/two-factor/setup"); }; return ( diff --git a/web/packages/new/photos/services/collection.ts b/web/packages/new/photos/services/collection.ts index 5e2a411e10..1a5a9fe54f 100644 --- a/web/packages/new/photos/services/collection.ts +++ b/web/packages/new/photos/services/collection.ts @@ -1,7 +1,7 @@ import { encryptBoxB64 } from "@/base/crypto"; import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import { apiURL } from "@/base/origins"; -import { SUB_TYPE, type Collection } from "@/media/collection"; +import { CollectionSubType, type Collection } from "@/media/collection"; import { type EnteFile } from "@/media/file"; import { ItemVisibility } from "@/media/file-metadata"; import { batch } from "@/utils/array"; @@ -32,7 +32,7 @@ export const ALL_SECTION = 0; export const isDefaultHiddenCollection = (collection: Collection) => // TODO: Need to audit the types // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - collection.magicMetadata?.data.subType === SUB_TYPE.DEFAULT_HIDDEN; + collection.magicMetadata?.data.subType == CollectionSubType.defaultHidden; /** * Extract the IDs of all the "default" hidden collections. diff --git a/web/packages/new/photos/services/collection/ui.ts b/web/packages/new/photos/services/collection/ui.ts index 6b9340def6..104fbe7352 100644 --- a/web/packages/new/photos/services/collection/ui.ts +++ b/web/packages/new/photos/services/collection/ui.ts @@ -1,12 +1,10 @@ +import type { CollectionType } from "@/media/collection"; import type { EnteFile } from "@/media/file"; export type CollectionSummaryType = - | "folder" - | "favorites" - | "album" + | CollectionType | "archive" | "trash" - | "uncategorized" | "all" | "outgoingShare" | "incomingShareViewer" diff --git a/web/packages/new/photos/services/collections.ts b/web/packages/new/photos/services/collections.ts index 8dd08a6d58..13e8dba874 100644 --- a/web/packages/new/photos/services/collections.ts +++ b/web/packages/new/photos/services/collections.ts @@ -29,7 +29,7 @@ import { } from "@/new/photos/services/files"; import HTTPService from "@ente/shared/network/HTTPService"; import localForage from "@ente/shared/storage/localForage"; -import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; +import { getData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { isHiddenCollection } from "./collection"; @@ -200,7 +200,7 @@ export const getCollectionWithSecrets = async ( masterKey: string, ): Promise => { const cryptoWorker = await sharedCryptoWorker(); - const userID = getData(LS_KEYS.USER).id; + const userID = getData("user").id; let collectionKey: string; if (collection.owner.id === userID) { collectionKey = await cryptoWorker.decryptB64( @@ -209,7 +209,7 @@ export const getCollectionWithSecrets = async ( masterKey, ); } else { - const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes = getData("keyAttributes"); const secretKey = await cryptoWorker.decryptB64( keyAttributes.encryptedSecretKey, keyAttributes.secretKeyDecryptionNonce, diff --git a/web/packages/new/photos/services/files.ts b/web/packages/new/photos/services/files.ts index 61c1e4e90b..d46fd10646 100644 --- a/web/packages/new/photos/services/files.ts +++ b/web/packages/new/photos/services/files.ts @@ -33,7 +33,7 @@ export const getAllLocalFiles = async () => * "hidden" to get it to instead return hidden files that we know about locally. */ export const getLocalFiles = async (type: "normal" | "hidden" = "normal") => { - const tableName = type === "normal" ? FILES_TABLE : HIDDEN_FILES_TABLE; + const tableName = type == "normal" ? FILES_TABLE : HIDDEN_FILES_TABLE; const files: EnteFile[] = (await localForage.getItem(tableName)) ?? []; return files; @@ -48,7 +48,7 @@ export const setLocalFiles = async ( type: "normal" | "hidden", files: EnteFile[], ) => { - const tableName = type === "normal" ? FILES_TABLE : HIDDEN_FILES_TABLE; + const tableName = type == "normal" ? FILES_TABLE : HIDDEN_FILES_TABLE; await localForage.setItem(tableName, files); }; diff --git a/web/packages/new/photos/services/ml/ml-data.ts b/web/packages/new/photos/services/ml/ml-data.ts index fc6313a25f..a152f12d1b 100644 --- a/web/packages/new/photos/services/ml/ml-data.ts +++ b/web/packages/new/photos/services/ml/ml-data.ts @@ -1,10 +1,10 @@ import { decryptBlob } from "@/base/crypto"; import log from "@/base/log"; +import { fetchFilesData, putFileData } from "@/gallery/services/file-data"; import type { EnteFile } from "@/media/file"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; import { gunzip, gzip } from "../../utils/gzip"; -import { fetchFileData, putFileData } from "../file-data"; import { type RemoteCLIPIndex } from "./clip"; import { type RemoteFaceIndex } from "./face"; @@ -153,7 +153,7 @@ const ParsedRemoteMLData = z.object({ export const fetchMLData = async ( filesByID: Map, ): Promise> => { - const remoteFileDatas = await fetchFileData("mldata", [ + const remoteFileDatas = await fetchFilesData("mldata", [ ...filesByID.keys(), ]); diff --git a/web/packages/new/photos/services/sync.ts b/web/packages/new/photos/services/sync.ts index 450f7ba1f6..141fd97862 100644 --- a/web/packages/new/photos/services/sync.ts +++ b/web/packages/new/photos/services/sync.ts @@ -63,8 +63,13 @@ interface SyncCallectionAndFilesOpts { /** * Called when saved collections, both normal and hidden, are (potentially) * updated. + * + * The callback is passed all the collections (as {@link collections}), and + * also their splits into normal ({@link normalCollections}) and hidden + * ({@link hiddenCollections}). */ onSetCollections: ( + collections: Collection[], normalCollections: Collection[], hiddenCollections: Collection[], ) => void; @@ -118,7 +123,7 @@ export const syncCollectionAndFiles = async ( collections, isHiddenCollection, ); - opts?.onSetCollections(normalCollections, hiddenCollections); + opts?.onSetCollections(collections, normalCollections, hiddenCollections); const didUpdateNormalFiles = await syncFiles( "normal", normalCollections, diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts index fef76544e6..40c3d6ee3f 100644 --- a/web/packages/new/photos/services/user-details.ts +++ b/web/packages/new/photos/services/user-details.ts @@ -7,7 +7,7 @@ import { nullishToZero, nullToUndefined, } from "@/utils/transform"; -import { getData, LS_KEYS, setLSUser } from "@ente/shared/storage/localStorage"; +import { getData, setLSUser } from "@ente/shared/storage/localStorage"; import { z } from "zod"; /** @@ -243,7 +243,7 @@ export const syncUserDetails = async () => { // // Added Nov 2024, and can be removed after a while (tag: Migration). - const oldLSUser = getData(LS_KEYS.USER) as unknown; + const oldLSUser = getData("user") as unknown; const hasMatchingEmail = oldLSUser && typeof oldLSUser == "object" && @@ -253,7 +253,7 @@ export const syncUserDetails = async () => { if (!hasMatchingEmail) { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - await setLSUser({ ...getData(LS_KEYS.USER), email: userDetails.email }); + await setLSUser({ ...getData("user"), email: userDetails.email }); throw new Error("EmailĀ in local storage did not match user details"); } }; diff --git a/web/packages/shared/constants/pages.tsx b/web/packages/shared/constants/pages.tsx deleted file mode 100644 index 6bcd0e27d9..0000000000 --- a/web/packages/shared/constants/pages.tsx +++ /dev/null @@ -1,31 +0,0 @@ -export enum PHOTOS_PAGES { - CHANGE_PASSWORD = "/change-password", - CREDENTIALS = "/credentials", - GALLERY = "/gallery", - GENERATE = "/generate", - LOGIN = "/login", - RECOVER = "/recover", - SIGNUP = "/signup", - TWO_FACTOR_SETUP = "/two-factor/setup", - TWO_FACTOR_VERIFY = "/two-factor/verify", - TWO_FACTOR_RECOVER = "/two-factor/recover", - VERIFY = "/verify", - ROOT = "/", - SHARED_ALBUMS = "/shared-albums", -} - -export enum AUTH_PAGES { - CHANGE_PASSWORD = "/change-password", - CREDENTIALS = "/credentials", - GALLERY = "/gallery", - GENERATE = "/generate", - LOGIN = "/login", - RECOVER = "/recover", - SIGNUP = "/signup", - TWO_FACTOR_SETUP = "/two-factor/setup", - TWO_FACTOR_VERIFY = "/two-factor/verify", - TWO_FACTOR_RECOVER = "/two-factor/recover", - VERIFY = "/verify", - ROOT = "/", - AUTH = "/auth", -} diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 024f1ac0c2..94fd2fbbc2 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -2,14 +2,9 @@ import { setRecoveryKey } from "@/accounts/services/user"; import { sharedCryptoWorker } from "@/base/crypto"; import log from "@/base/log"; import { masterKeyFromSession } from "@/base/session"; -import { - LS_KEYS, - getData, - setData, - setLSUser, -} from "@ente/shared/storage/localStorage"; +import { getData, setData, setLSUser } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; -import { SESSION_KEYS, setKey } from "@ente/shared/storage/sessionStorage"; +import { type SessionKey, setKey } from "@ente/shared/storage/sessionStorage"; import { getActualKey } from "@ente/shared/user"; import type { KeyAttributes } from "@ente/shared/user/types"; @@ -23,7 +18,7 @@ export async function decryptAndStoreToken( masterKey: string, ) { const cryptoWorker = await sharedCryptoWorker(); - const user = getData(LS_KEYS.USER); + const user = getData("user"); let decryptedToken = null; const { encryptedToken } = user; if (encryptedToken && encryptedToken.length > 0) { @@ -76,7 +71,7 @@ export async function generateAndSaveIntermediateKeyAttributes( opsLimit: intermediateKek.opsLimit, memLimit: intermediateKek.memLimit, }); - setData(LS_KEYS.KEY_ATTRIBUTES, intermediateKeyAttributes); + setData("keyAttributes", intermediateKeyAttributes); return intermediateKeyAttributes; } @@ -99,7 +94,7 @@ export const generateLoginSubKey = async (kek: string) => { }; export const saveKeyInSessionStore = async ( - keyType: SESSION_KEYS, + keyType: SessionKey, key: string, fromDesktop?: boolean, ) => { @@ -108,7 +103,7 @@ export const saveKeyInSessionStore = async ( await cryptoWorker.generateKeyAndEncryptToB64(key); setKey(keyType, sessionKeyAttributes); const electron = globalThis.electron; - if (electron && !fromDesktop && keyType === SESSION_KEYS.ENCRYPTION_KEY) { + if (electron && !fromDesktop && keyType == "encryptionKey") { electron.saveMasterKeyB64(key); } }; @@ -124,7 +119,7 @@ export const getRecoveryKey = async () => { try { const cryptoWorker = await sharedCryptoWorker(); - const keyAttributes: KeyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes: KeyAttributes = getData("keyAttributes"); const { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce, @@ -152,7 +147,7 @@ export const getRecoveryKey = async () => { // sign up async function createNewRecoveryKey() { const masterKey = await getActualKey(); - const existingAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const existingAttributes = getData("keyAttributes"); const cryptoWorker = await sharedCryptoWorker(); @@ -177,7 +172,7 @@ async function createNewRecoveryKey() { existingAttributes, recoveryKeyAttributes, ); - setData(LS_KEYS.KEY_ATTRIBUTES, updatedKeyAttributes); + setData("keyAttributes", updatedKeyAttributes); return recoveryKey; } @@ -193,7 +188,7 @@ export const decryptDeleteAccountChallenge = async ( ) => { const cryptoWorker = await sharedCryptoWorker(); const masterKey = await masterKeyFromSession(); - const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES); + const keyAttributes = getData("keyAttributes"); const secretKey = await cryptoWorker.decryptBoxB64( { encryptedData: keyAttributes.encryptedSecretKey, diff --git a/web/packages/shared/storage/localStorage/helpers.ts b/web/packages/shared/storage/localStorage/helpers.ts index 265546dfc7..85154c4c96 100644 --- a/web/packages/shared/storage/localStorage/helpers.ts +++ b/web/packages/shared/storage/localStorage/helpers.ts @@ -1,28 +1,26 @@ -import { LS_KEYS, getData, setData } from "."; +import { getData, setData } from "."; export const getToken = (): string => { - const token = getData(LS_KEYS.USER)?.token; + const token = getData("user")?.token; return token; }; -export const isFirstLogin = () => - getData(LS_KEYS.IS_FIRST_LOGIN)?.status ?? false; +export const isFirstLogin = () => getData("isFirstLogin")?.status ?? false; export function setIsFirstLogin(status: boolean) { - setData(LS_KEYS.IS_FIRST_LOGIN, { status }); + setData("isFirstLogin", { status }); } -export const justSignedUp = () => - getData(LS_KEYS.JUST_SIGNED_UP)?.status ?? false; +export const justSignedUp = () => getData("justSignedUp")?.status ?? false; export function setJustSignedUp(status: boolean) { - setData(LS_KEYS.JUST_SIGNED_UP, { status }); + setData("justSignedUp", { status }); } export function getLocalReferralSource() { - return getData(LS_KEYS.REFERRAL_SOURCE)?.source; + return getData("referralSource")?.source; } export function setLocalReferralSource(source: string) { - setData(LS_KEYS.REFERRAL_SOURCE, { source }); + setData("referralSource", { source }); } diff --git a/web/packages/shared/storage/localStorage/index.ts b/web/packages/shared/storage/localStorage/index.ts index 56baa1ab0b..4aacae314d 100644 --- a/web/packages/shared/storage/localStorage/index.ts +++ b/web/packages/shared/storage/localStorage/index.ts @@ -1,30 +1,30 @@ import { getKVS, removeKV, setKV } from "@/base/kv"; import log from "@/base/log"; -export enum LS_KEYS { - USER = "user", - KEY_ATTRIBUTES = "keyAttributes", - ORIGINAL_KEY_ATTRIBUTES = "originalKeyAttributes", - IS_FIRST_LOGIN = "isFirstLogin", - JUST_SIGNED_UP = "justSignedUp", - SHOW_BACK_BUTTON = "showBackButton", - EXPORT = "export", +export type LocalStorageKey = + | "user" + | "keyAttributes" + | "originalKeyAttributes" + | "isFirstLogin" + | "justSignedUp" + | "showBackButton" + | "export" // LOGS = "logs", // Migrated to (and only used by) useCollectionsSortByLocalState. - COLLECTION_SORT_BY = "collectionSortBy", + | "collectionSortBy" // Moved to the new wrapper @/base/local-storage // LOCALE = 'locale', - SRP_SETUP_ATTRIBUTES = "srpSetupAttributes", - SRP_ATTRIBUTES = "srpAttributes", - REFERRAL_SOURCE = "referralSource", -} + | "srpSetupAttributes" + | "srpAttributes" + | "referralSource"; -export const setData = (key: LS_KEYS, value: object) => +export const setData = (key: LocalStorageKey, value: object) => localStorage.setItem(key, JSON.stringify(value)); -export const removeData = (key: LS_KEYS) => localStorage.removeItem(key); +export const removeData = (key: LocalStorageKey) => + localStorage.removeItem(key); -export const getData = (key: LS_KEYS) => { +export const getData = (key: LocalStorageKey) => { try { if ( typeof localStorage === "undefined" || @@ -48,7 +48,7 @@ export const getData = (key: LS_KEYS) => { // Creating a new function here to act as a funnel point. export const setLSUser = async (user: object) => { await migrateKVToken(user); - setData(LS_KEYS.USER, user); + setData("user", user); }; /** @@ -66,7 +66,7 @@ export const migrateKVToken = async (user: unknown) => { // Throw an error if the data is in local storage but not in IndexedDB. This // is a pre-cursor to inlining this code. // TODO(REL): Remove this sanity check after a few days. - const oldLSUser = getData(LS_KEYS.USER); + const oldLSUser = getData("user"); const wasMissing = oldLSUser && typeof oldLSUser == "object" && @@ -103,7 +103,7 @@ export const migrateKVToken = async (user: unknown) => { * token in local storage, then it should also be present in IndexedDB. */ export const isLocalStorageAndIndexedDBMismatch = async () => { - const oldLSUser = getData(LS_KEYS.USER); + const oldLSUser = getData("user"); return ( oldLSUser && typeof oldLSUser == "object" && diff --git a/web/packages/shared/storage/sessionStorage/index.ts b/web/packages/shared/storage/sessionStorage/index.ts index b3de32d69a..7fbef7381a 100644 --- a/web/packages/shared/storage/sessionStorage/index.ts +++ b/web/packages/shared/storage/sessionStorage/index.ts @@ -1,16 +1,13 @@ -export enum SESSION_KEYS { - ENCRYPTION_KEY = "encryptionKey", - KEY_ENCRYPTION_KEY = "keyEncryptionKey", -} +export type SessionKey = "encryptionKey" | "keyEncryptionKey"; -export const setKey = (key: SESSION_KEYS, value: object) => +export const setKey = (key: SessionKey, value: object) => sessionStorage.setItem(key, JSON.stringify(value)); -export const getKey = (key: SESSION_KEYS) => { +export const getKey = (key: SessionKey) => { const value = sessionStorage.getItem(key); return value && JSON.parse(value); }; -export const removeKey = (key: SESSION_KEYS) => sessionStorage.removeItem(key); +export const removeKey = (key: SessionKey) => sessionStorage.removeItem(key); export const clearKeys = () => sessionStorage.clear(); diff --git a/web/packages/shared/user/index.ts b/web/packages/shared/user/index.ts index a5a4a79070..572f90db7f 100644 --- a/web/packages/shared/user/index.ts +++ b/web/packages/shared/user/index.ts @@ -1,13 +1,12 @@ import { sharedCryptoWorker } from "@/base/crypto"; import type { B64EncryptionResult } from "@/base/crypto/libsodium"; import { CustomError } from "@ente/shared/error"; -import { getKey, SESSION_KEYS } from "@ente/shared/storage/sessionStorage"; +import { getKey } from "@ente/shared/storage/sessionStorage"; export const getActualKey = async () => { try { - const encryptionKeyAttributes: B64EncryptionResult = getKey( - SESSION_KEYS.ENCRYPTION_KEY, - ); + const encryptionKeyAttributes: B64EncryptionResult = + getKey("encryptionKey"); const cryptoWorker = await sharedCryptoWorker(); const key = await cryptoWorker.decryptB64( diff --git a/web/packages/utils/array.ts b/web/packages/utils/array.ts index 0da52c0a30..248100c763 100644 --- a/web/packages/utils/array.ts +++ b/web/packages/utils/array.ts @@ -8,7 +8,7 @@ * then we sort by this key. Since the key is random, the sorted array will have * the original elements in a random order. */ -export const shuffled = (xs: T[]) => +export const shuffled = (xs: T[]): T[] => xs .map((x) => [Math.random(), x]) .sort() @@ -24,7 +24,9 @@ export const shuffled = (xs: T[]) => * If none of the strings are non-empty, or if there are no strings in the given * array, return undefined. */ -export const firstNonEmpty = (ss: (string | undefined)[]) => { +export const firstNonEmpty = ( + ss: (string | undefined)[], +): string | undefined => { for (const s of ss) if (s && s.length > 0) return s; return undefined; }; @@ -35,7 +37,7 @@ export const firstNonEmpty = (ss: (string | undefined)[]) => { * * @param as An array of {@link Uint8Array}. */ -export const mergeUint8Arrays = (as: Uint8Array[]) => { +export const mergeUint8Arrays = (as: Uint8Array[]): Uint8Array => { // A longer but better performing replacement of // // new Uint8Array(as.reduce((acc, x) => acc.concat(...x), [])) diff --git a/web/packages/utils/ensure.ts b/web/packages/utils/ensure.ts index 7568de607f..7d02ee0277 100644 --- a/web/packages/utils/ensure.ts +++ b/web/packages/utils/ensure.ts @@ -1,3 +1,13 @@ +/** + * Throw an exception if the given value {@link v} is false-y. + * + * This is a variant of {@link assertionFailed}, except it always throws, not + * just in dev builds, if the given value is falsey. + */ +export const ensurePrecondition = (v: unknown): void => { + if (!v) throw new Error("Precondition failed"); +}; + /** * Throw an exception if the given value is not a string. */ diff --git a/web/packages/utils/parse.ts b/web/packages/utils/parse.ts index 5e0f3c0739..31300a5c4d 100644 --- a/web/packages/utils/parse.ts +++ b/web/packages/utils/parse.ts @@ -13,7 +13,7 @@ * > * > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN */ -export const maybeParseInt = (s: string) => { +export const maybeParseInt = (s: string): number | undefined => { const n = parseInt(s, 10); return Number.isNaN(n) ? undefined : n; }; diff --git a/web/packages/utils/promise.ts b/web/packages/utils/promise.ts index bef7e9819e..e6670d076b 100644 --- a/web/packages/utils/promise.ts +++ b/web/packages/utils/promise.ts @@ -4,7 +4,7 @@ * This function is a promisified `setTimeout`. It returns a promise that * resolves after {@link ms} milliseconds. */ -export const wait = (ms: number) => +export const wait = (ms: number): Promise => new Promise((resolve) => setTimeout(resolve, ms)); /** @@ -65,7 +65,10 @@ export const wait = (ms: number) => * scratch for now. Indeed, I've spent more time writing about the function * than the function itself. */ -export const throttled = (underlying: () => Promise, period: number) => { +export const throttled = ( + underlying: () => Promise, + period: number, +): (() => void) => { let pending = 0; const f = () => { @@ -91,7 +94,10 @@ export const throttled = (underlying: () => Promise, period: number) => { * settled, just its eventual state will be ignored if it gets fullfilled or * rejected after we've already timed out. */ -export const withTimeout = async (promise: Promise, ms: number) => { +export const withTimeout = async ( + promise: Promise, + ms: number, +): Promise => { let timeoutId: ReturnType; const rejectOnTimeout = new Promise((_, reject) => { timeoutId = setTimeout( diff --git a/web/packages/utils/transform.ts b/web/packages/utils/transform.ts index 60053b0fae..905977c6d7 100644 --- a/web/packages/utils/transform.ts +++ b/web/packages/utils/transform.ts @@ -7,9 +7,9 @@ export const nullToUndefined = (v: T | null | undefined): T | undefined => /** * Convert `null` and `undefined` to `0`, passthrough everything else unchanged. */ -export const nullishToZero = (v: number | null | undefined) => v ?? 0; +export const nullishToZero = (v: number | null | undefined): number => v ?? 0; /** * Convert `null` and `undefined` to `[]`, passthrough everything else unchanged. */ -export const nullishToEmpty = (v: T[] | null | undefined) => v ?? []; +export const nullishToEmpty = (v: T[] | null | undefined): T[] => v ?? [];