Procházet zdrojové kódy

Resolved merge conflicts

ashilkn před 2 roky
rodič
revize
afa0309f39
84 změnil soubory, kde provedl 1122 přidání a 595 odebrání
  1. 71 70
      android/app/src/main/AndroidManifest.xml
  2. binární
      android/app/src/main/res/drawable-hdpi/android12splash.png
  3. binární
      android/app/src/main/res/drawable-hdpi/splash.png
  4. binární
      android/app/src/main/res/drawable-mdpi/android12splash.png
  5. binární
      android/app/src/main/res/drawable-mdpi/splash.png
  6. binární
      android/app/src/main/res/drawable-night-hdpi/android12splash.png
  7. binární
      android/app/src/main/res/drawable-night-hdpi/splash.png
  8. binární
      android/app/src/main/res/drawable-night-mdpi/android12splash.png
  9. binární
      android/app/src/main/res/drawable-night-mdpi/splash.png
  10. 1 1
      android/app/src/main/res/drawable-night-v21/launch_background.xml
  11. binární
      android/app/src/main/res/drawable-night-xhdpi/android12splash.png
  12. binární
      android/app/src/main/res/drawable-night-xhdpi/splash.png
  13. binární
      android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
  14. binární
      android/app/src/main/res/drawable-night-xxhdpi/splash.png
  15. binární
      android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
  16. binární
      android/app/src/main/res/drawable-night-xxxhdpi/splash.png
  17. 1 1
      android/app/src/main/res/drawable-night/launch_background.xml
  18. 1 1
      android/app/src/main/res/drawable-v21/launch_background.xml
  19. binární
      android/app/src/main/res/drawable-xhdpi/android12splash.png
  20. binární
      android/app/src/main/res/drawable-xhdpi/splash.png
  21. binární
      android/app/src/main/res/drawable-xxhdpi/android12splash.png
  22. binární
      android/app/src/main/res/drawable-xxhdpi/splash.png
  23. binární
      android/app/src/main/res/drawable-xxxhdpi/android12splash.png
  24. binární
      android/app/src/main/res/drawable-xxxhdpi/splash.png
  25. 1 1
      android/app/src/main/res/drawable/launch_background.xml
  26. 1 0
      android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml
  27. 3 1
      android/app/src/main/res/values-night-v31/styles.xml
  28. 2 1
      android/app/src/main/res/values-night/styles.xml
  29. 3 1
      android/app/src/main/res/values-v31/styles.xml
  30. binární
      assets/splash-screen-dark.png
  31. binární
      assets/splash-screen-light.png
  32. 9 9
      ios/Podfile.lock
  33. 3 3
      ios/Runner.xcodeproj/project.pbxproj
  34. 2 32
      ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json
  35. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  36. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
  37. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
  38. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png
  39. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png
  40. binární
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png
  41. 2 2
      ios/Runner/Base.lproj/LaunchScreen.storyboard
  42. 14 15
      ios/Runner/Info.plist
  43. 27 44
      lib/app.dart
  44. 6 2
      lib/db/files_db.dart
  45. 3 1
      lib/extensions/input_formatter.dart
  46. 2 0
      lib/main.dart
  47. 5 3
      lib/services/app_lifecycle_service.dart
  48. 3 1
      lib/services/collections_service.dart
  49. 13 0
      lib/services/memories_service.dart
  50. 7 3
      lib/services/storage_bonus_service.dart
  51. 1 1
      lib/services/sync_service.dart
  52. 9 0
      lib/theme/colors.dart
  53. 68 3
      lib/theme/text_style.dart
  54. 143 0
      lib/ui/actions/file/file_actions.dart
  55. 5 2
      lib/ui/collection_action_sheet.dart
  56. 5 1
      lib/ui/collections/archived_collections_button_widget.dart
  57. 100 0
      lib/ui/components/album_horizontal_list_widget.dart
  58. 13 1
      lib/ui/components/notification_widget.dart
  59. 23 5
      lib/ui/growth/apply_code_screen.dart
  60. 163 0
      lib/ui/growth/code_success_screen.dart
  61. 52 0
      lib/ui/growth/referral_code_widget.dart
  62. 16 44
      lib/ui/growth/referral_screen.dart
  63. 10 6
      lib/ui/growth/storage_details_screen.dart
  64. 62 13
      lib/ui/home/memories_widget.dart
  65. 2 4
      lib/ui/home_widget.dart
  66. 5 4
      lib/ui/huge_listview/lazy_loading_gallery.dart
  67. 20 13
      lib/ui/settings_page.dart
  68. 0 1
      lib/ui/sharing/share_collection_page.dart
  69. 3 2
      lib/ui/viewer/actions/delete_empty_albums.dart
  70. 1 1
      lib/ui/viewer/actions/file_selection_actions_widget.dart
  71. 104 0
      lib/ui/viewer/actions/file_viewer.dart
  72. 2 0
      lib/ui/viewer/file/detail_page.dart
  73. 17 112
      lib/ui/viewer/file/fading_app_bar.dart
  74. 23 41
      lib/ui/viewer/file/fading_bottom_bar.dart
  75. 3 42
      lib/ui/viewer/gallery/archive_page.dart
  76. 13 37
      lib/ui/viewer/gallery/collection_page.dart
  77. 7 28
      lib/ui/viewer/gallery/trash_page.dart
  78. 4 0
      lib/utils/delete_file_util.dart
  79. 3 2
      lib/utils/dialog_util.dart
  80. 6 4
      lib/utils/file_uploader.dart
  81. 16 0
      lib/utils/intent_util.dart
  82. 24 24
      pubspec.lock
  83. 12 3
      pubspec.yaml
  84. 7 9
      run.sh

+ 71 - 70
android/app/src/main/AndroidManifest.xml

@@ -1,107 +1,108 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:tools="http://schemas.android.com/tools"
-          package="io.ente.photos">
+    xmlns:tools="http://schemas.android.com/tools"
+    package="io.ente.photos">
     <application android:name="${applicationName}"
-                 android:label="@string/app_name"
-                 android:icon="@mipmap/launcher_icon"
-                 android:usesCleartextTraffic="true"
-                 android:requestLegacyExternalStorage="true"
-                 android:allowBackup="false"
-                 android:fullBackupContent="false"
-                 android:largeHeap="true">
+        android:label="@string/app_name"
+        android:icon="@mipmap/launcher_icon"
+        android:usesCleartextTraffic="true"
+        android:requestLegacyExternalStorage="true"
+        android:allowBackup="false"
+        android:fullBackupContent="false"
+        android:largeHeap="true">
 
         <activity android:name=".MainActivity" android:launchMode="singleTop"
-                  android:theme="@style/LaunchTheme"
-                  android:exported="true"
-                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
-                  android:hardwareAccelerated="true"
-                  android:windowSoftInputMode="adjustResize">
-                  <intent-filter>
-                    <action android:name="android.intent.action.MAIN"/>
-                    <category android:name="android.intent.category.LAUNCHER"/>
-                </intent-filter>
-                <!-- <intent-filter>
-                    <action android:name="android.intent.action.VIEW" />
-                    <category android:name="android.intent.category.DEFAULT" />
-                    <data android:mimeType="image/*" />
-                    <data android:mimeType="video/*" />
-                </intent-filter> -->
-                <intent-filter>
-                    <action android:name="android.intent.action.PICK" />
-                    <category android:name="android.intent.category.DEFAULT" />
-                    <data android:mimeType="image/*" />
-                    <data android:mimeType="video/*" />
-                </intent-filter>
-                <intent-filter>
-                    <action android:name="android.intent.action.GET_CONTENT" />
-                    <category android:name="android.intent.category.DEFAULT" />
-                    <category android:name="android.intent.category.OPENABLE" />
-                    <data android:mimeType="image/*" />
-                    <data android:mimeType="video/*" />
-                </intent-filter>
-    
-              <!-- file provider to share files having a file:// URI -->
+            android:theme="@style/LaunchTheme"
+            android:exported="true"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.PICK" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.GET_CONTENT" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.OPENABLE" />
+                <data android:mimeType="image/*" />
+                <data android:mimeType="video/*" />
+            </intent-filter>
+
+            <!-- file provider to share files having a file:// URI -->
 
-            <!--Filter to support sharing images into our app-->
+            <!--Filter
+            to support sharing images into our app-->
             <intent-filter android:label="@string/backup">
-                <action android:name="android.intent.action.SEND"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:mimeType="image/*"/>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
             </intent-filter>
 
             <intent-filter android:label="@string/backup">
-                <action android:name="android.intent.action.SEND_MULTIPLE"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:mimeType="image/*"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="image/*" />
             </intent-filter>
 
             <intent-filter android:label="@string/backup">
-                <action android:name="android.intent.action.SEND"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:mimeType="video/*"/>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
             </intent-filter>
 
             <intent-filter android:label="@string/backup">
-                <action android:name="android.intent.action.SEND_MULTIPLE"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:mimeType="video/*"/>
+                <action android:name="android.intent.action.SEND_MULTIPLE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="video/*" />
             </intent-filter>
 
         </activity>
 
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
-        <meta-data android:name="flutterEmbedding" android:value="2"/>
+        <meta-data android:name="flutterEmbedding" android:value="2" />
         <meta-data android:name="asset_statements"
-                   android:resource="@string/asset_statements"/>
+            android:resource="@string/asset_statements" />
         <meta-data android:name="io.sentry.dsn"
-                   android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4"/>
+            android:value="https://2235e5c99219488ea93da34b9ac1cb68@sentry.ente.io/4" />
         <meta-data android:name="firebase_analytics_collection_deactivated"
-                   android:value="true"/>
+            android:value="true" />
     </application>
 
     <!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
     <!-- https://developer.android.com/training/package-visibility/use-cases -->
     <queries>
         <intent>
-            <action android:name="android.intent.action.SENDTO"/>
-            <data android:scheme="mailto"/>
+            <action android:name="android.intent.action.SENDTO" />
+            <data android:scheme="mailto" />
         </intent>
     </queries>
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.MANAGE_MEDIA"/>
-    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.MANAGE_MEDIA" />
+    <uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
     <uses-permission
-            android:name="android.permission.READ_MEDIA_IMAGES"/> <!-- If you want to read images-->
+        android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- If you want to read images-->
     <uses-permission
-            android:name="android.permission.READ_MEDIA_VIDEO"/> <!-- If you want to read videos-->
+        android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- If you want to read videos-->
     <uses-permission
-            android:name="android.permission.READ_EXTERNAL_STORAGE"
-            android:maxSdkVersion="32"/>
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
     <uses-permission
-            android:name="android.permission.WRITE_EXTERNAL_STORAGE"
-            android:maxSdkVersion="29"
-            tools:ignore="ScopedStorage"/>
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="com.android.vending.BILLING"/>
+        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
+        android:maxSdkVersion="29"
+        tools:ignore="ScopedStorage" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="com.android.vending.BILLING" />
 </manifest>

binární
android/app/src/main/res/drawable-hdpi/android12splash.png


binární
android/app/src/main/res/drawable-hdpi/splash.png


binární
android/app/src/main/res/drawable-mdpi/android12splash.png


binární
android/app/src/main/res/drawable-mdpi/splash.png


binární
android/app/src/main/res/drawable-night-hdpi/android12splash.png


binární
android/app/src/main/res/drawable-night-hdpi/splash.png


binární
android/app/src/main/res/drawable-night-mdpi/android12splash.png


binární
android/app/src/main/res/drawable-night-mdpi/splash.png


+ 1 - 1
android/app/src/main/res/drawable-night-v21/launch_background.xml

@@ -6,4 +6,4 @@
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
-</layer-list>
+</layer-list>

binární
android/app/src/main/res/drawable-night-xhdpi/android12splash.png


binární
android/app/src/main/res/drawable-night-xhdpi/splash.png


binární
android/app/src/main/res/drawable-night-xxhdpi/android12splash.png


binární
android/app/src/main/res/drawable-night-xxhdpi/splash.png


binární
android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png


binární
android/app/src/main/res/drawable-night-xxxhdpi/splash.png


+ 1 - 1
android/app/src/main/res/drawable-night/launch_background.xml

@@ -6,4 +6,4 @@
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
-</layer-list>
+</layer-list>

+ 1 - 1
android/app/src/main/res/drawable-v21/launch_background.xml

@@ -6,4 +6,4 @@
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
-</layer-list>
+</layer-list>

binární
android/app/src/main/res/drawable-xhdpi/android12splash.png


binární
android/app/src/main/res/drawable-xhdpi/splash.png


binární
android/app/src/main/res/drawable-xxhdpi/android12splash.png


binární
android/app/src/main/res/drawable-xxhdpi/splash.png


binární
android/app/src/main/res/drawable-xxxhdpi/android12splash.png


binární
android/app/src/main/res/drawable-xxxhdpi/splash.png


+ 1 - 1
android/app/src/main/res/drawable/launch_background.xml

@@ -6,4 +6,4 @@
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
-</layer-list>
+</layer-list>

+ 1 - 0
android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml

@@ -2,4 +2,5 @@
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
   <background android:drawable="@color/ic_launcher_background"/>
   <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+  <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
 </adaptive-icon>

+ 3 - 1
android/app/src/main/res/values-night-v31/styles.xml

@@ -4,7 +4,9 @@
     <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
         <item name="android:windowSplashScreenBackground">#000000</item>
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
@@ -15,4 +17,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
-</resources>
+</resources>

+ 2 - 1
android/app/src/main/res/values-night/styles.xml

@@ -7,6 +7,7 @@
         <item name="android:windowBackground">@drawable/launch_background</item>
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
@@ -17,4 +18,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
-</resources>
+</resources>

+ 3 - 1
android/app/src/main/res/values-v31/styles.xml

@@ -4,7 +4,9 @@
     <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
         <item name="android:windowSplashScreenBackground">#ffffff</item>
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
@@ -15,4 +17,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
-</resources>
+</resources>

binární
assets/splash-screen-dark.png


binární
assets/splash-screen-light.png


+ 9 - 9
ios/Podfile.lock

@@ -3,9 +3,9 @@ PODS:
     - Flutter
   - camera_avfoundation (0.0.1):
     - Flutter
-  - connectivity (0.0.1):
+  - connectivity_plus (0.0.1):
     - Flutter
-    - Reachability
+    - ReachabilitySwift
   - device_info (0.0.1):
     - Flutter
   - Firebase/CoreOnly (10.3.0):
@@ -136,7 +136,7 @@ PODS:
     - Flutter
     - FlutterMacOS
   - PromisesObjC (2.1.1)
-  - Reachability (3.2)
+  - ReachabilitySwift (5.0.0)
   - receive_sharing_intent (0.0.1):
     - Flutter
   - SDWebImage (5.15.2):
@@ -178,7 +178,7 @@ PODS:
 DEPENDENCIES:
   - background_fetch (from `.symlinks/plugins/background_fetch/ios`)
   - camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
-  - connectivity (from `.symlinks/plugins/connectivity/ios`)
+  - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
   - device_info (from `.symlinks/plugins/device_info/ios`)
   - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
   - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
@@ -231,7 +231,7 @@ SPEC REPOS:
     - nanopb
     - OrderedSet
     - PromisesObjC
-    - Reachability
+    - ReachabilitySwift
     - SDWebImage
     - SDWebImageWebPCoder
     - Sentry
@@ -242,8 +242,8 @@ EXTERNAL SOURCES:
     :path: ".symlinks/plugins/background_fetch/ios"
   camera_avfoundation:
     :path: ".symlinks/plugins/camera_avfoundation/ios"
-  connectivity:
-    :path: ".symlinks/plugins/connectivity/ios"
+  connectivity_plus:
+    :path: ".symlinks/plugins/connectivity_plus/ios"
   device_info:
     :path: ".symlinks/plugins/device_info/ios"
   firebase_core:
@@ -320,7 +320,7 @@ EXTERNAL SOURCES:
 SPEC CHECKSUMS:
   background_fetch: bd64e544b303ee4cd4cf2fe8cb2187b72aecf9ca
   camera_avfoundation: 07c77549ea54ad95d8581be86617c094a46280d9
-  connectivity: c4130b2985d4ef6fd26f9702e886bd5260681467
+  connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
   device_info: d7d233b645a32c40dfdc212de5cf646ca482f175
   Firebase: f92fc551ead69c94168d36c2b26188263860acd9
   firebase_core: f95c8bbe65213d406d592573ad42a12d64849cb8
@@ -358,7 +358,7 @@ SPEC CHECKSUMS:
   path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
   photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
   PromisesObjC: ab77feca74fa2823e7af4249b8326368e61014cb
-  Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
+  ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
   receive_sharing_intent: c0d87310754e74c0f9542947e7cbdf3a0335a3b1
   SDWebImage: 8ab87d4b3e5cc4927bd47f78db6ceb0b94442577
   SDWebImageWebPCoder: 4851414d9f8894e328e8b97c93ea4f4f4e4418ae

+ 3 - 3
ios/Runner.xcodeproj/project.pbxproj

@@ -268,14 +268,14 @@
 				"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
 				"${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework",
 				"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.framework",
-				"${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework",
+				"${BUILT_PRODUCTS_DIR}/ReachabilitySwift/Reachability.framework",
 				"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
 				"${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
 				"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
 				"${BUILT_PRODUCTS_DIR}/Toast/Toast.framework",
 				"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
 				"${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework",
-				"${BUILT_PRODUCTS_DIR}/connectivity/connectivity.framework",
+				"${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework",
 				"${BUILT_PRODUCTS_DIR}/device_info/device_info.framework",
 				"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
 				"${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework",
@@ -331,7 +331,7 @@
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework",
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework",

+ 2 - 32
ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json

@@ -2,8 +2,7 @@
   "images" : [
     {
       "filename" : "background.png",
-      "idiom" : "universal",
-      "scale" : "1x"
+      "idiom" : "universal"
     },
     {
       "appearances" : [
@@ -13,36 +12,7 @@
         }
       ],
       "filename" : "darkbackground.png",
-      "idiom" : "universal",
-      "scale" : "1x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "2x"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "idiom" : "universal",
-      "scale" : "2x"
-    },
-    {
-      "idiom" : "universal",
-      "scale" : "3x"
-    },
-    {
-      "appearances" : [
-        {
-          "appearance" : "luminosity",
-          "value" : "dark"
-        }
-      ],
-      "idiom" : "universal",
-      "scale" : "3x"
+      "idiom" : "universal"
     }
   ],
   "info" : {

binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png


binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png


binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png


binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png


binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png


binární
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png


+ 2 - 2
ios/Runner/Base.lproj/LaunchScreen.storyboard

@@ -38,7 +38,7 @@
         </scene>
     </scenes>
     <resources>
-        <image name="LaunchImage" width="1024" height="1024"/>
+        <image name="LaunchImage" width="1152" height="1152"/>
         <image name="LaunchBackground" width="1" height="1"/>
     </resources>
-</document>
+</document>

+ 14 - 15
ios/Runner/Info.plist

@@ -23,19 +23,19 @@
 		<key>CFBundleSignature</key>
 		<string>????</string>
 		<key>MinimumOSVersion</key>
-        <string>12.0</string>
+		<string>12.0</string>
 		<key>LSApplicationQueriesSchemes</key>
-        <array>
-            <string>googlegmail</string>
-            <string>x-dispatch</string>
-            <string>readdle-spark</string>
-            <string>airmail</string>
-            <string>ms-outlook</string>
-            <string>ymail</string>
-            <string>fastmail</string>
-            <string>superhuman</string>
-            <string>protonmail</string>
-        </array>
+		<array>
+			<string>googlegmail</string>
+			<string>x-dispatch</string>
+			<string>readdle-spark</string>
+			<string>airmail</string>
+			<string>ms-outlook</string>
+			<string>ymail</string>
+			<string>fastmail</string>
+			<string>superhuman</string>
+			<string>protonmail</string>
+		</array>
 		<key>CFBundleURLTypes</key>
 		<array>
 			<dict>
@@ -67,8 +67,7 @@
 		<key>NSFaceIDUsageDescription</key>
 		<string>Please allow ente to lock itself with FaceID or TouchID</string>
 		<key>NSCameraUsageDescription</key>
-		<string>Please allow access to your camera so that you can take photos
-		within ente</string>
+		<string>Please allow access to your camera so that you can take photos within ente</string>
 		<key>NSPhotoLibraryUsageDescription</key>
 		<string>Please allow access to your photos so that ente can encrypt and back them up.</string>
 		<key>UIBackgroundModes</key>
@@ -102,5 +101,5 @@
 		<true/>
 		<key>UIApplicationSupportsIndirectInputEvents</key>
 		<true/>
-</dict>
+	</dict>
 </plist>

+ 27 - 44
lib/app.dart

@@ -4,16 +4,16 @@ import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:background_fetch/background_fetch.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:logging/logging.dart';
-import 'package:media_extension/media_extension.dart';
 import 'package:media_extension/media_extension_action_types.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/home_widget.dart';
+import "package:photos/ui/viewer/actions/file_viewer.dart";
+import "package:photos/utils/intent_util.dart";
 
 class EnteApp extends StatefulWidget {
   final Future<void> Function(String) runBackgroundTask;
@@ -31,63 +31,46 @@ class EnteApp extends StatefulWidget {
 
 class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
   final _logger = Logger("EnteAppState");
-  final _mediaExtensionPlugin = MediaExtension();
 
   @override
   void initState() {
     _logger.info('init App');
     super.initState();
+    setupIntentAction();
     WidgetsBinding.instance.addObserver(this);
   }
 
-  Future<bool> initIntentAction() async {
-    if (!Platform.isAndroid) {
-      AppLifecycleService.instance.setIntentAction(IntentAction.main);
-      return true;
-    }
-    IntentAction intentAction = IntentAction.main;
-    try {
-      final actionResult = await _mediaExtensionPlugin.getIntentAction();
-      intentAction = actionResult.action!;
-    } on PlatformException {
-      intentAction = IntentAction.main;
-    } catch (error) {
-      _logger.info(error);
-      intentAction = IntentAction.main;
-    }
-    if (intentAction == IntentAction.main) {
+  void setupIntentAction() async {
+    final mediaExtentionAction = Platform.isAndroid
+        ? await initIntentAction()
+        : MediaExtentionAction(action: IntentAction.main);
+    AppLifecycleService.instance.setMediaExtensionAction(mediaExtentionAction);
+    if (mediaExtentionAction.action == IntentAction.main) {
       _configureBackgroundFetch();
     }
-    AppLifecycleService.instance.setIntentAction(intentAction);
-    return true;
   }
 
   @override
   Widget build(BuildContext context) {
     if (Platform.isAndroid || kDebugMode) {
-      return FutureBuilder(
-        future: initIntentAction(),
-        builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
-          return snapshot.data != null && snapshot.data == true
-              ? AdaptiveTheme(
-                  light: lightThemeData,
-                  dark: darkThemeData,
-                  initial: AdaptiveThemeMode.system,
-                  builder: (lightTheme, dartTheme) => MaterialApp(
-                    title: "ente",
-                    themeMode: ThemeMode.system,
-                    theme: lightTheme,
-                    darkTheme: dartTheme,
-                    home: const HomeWidget(),
-                    debugShowCheckedModeBanner: false,
-                    builder: EasyLoading.init(),
-                    supportedLocales: AppLocalizations.supportedLocales,
-                    localizationsDelegates:
-                        AppLocalizations.localizationsDelegates,
-                  ),
-                )
-              : Container();
-        },
+      return AdaptiveTheme(
+        light: lightThemeData,
+        dark: darkThemeData,
+        initial: AdaptiveThemeMode.system,
+        builder: (lightTheme, dartTheme) => MaterialApp(
+          title: "ente",
+          themeMode: ThemeMode.system,
+          theme: lightTheme,
+          darkTheme: dartTheme,
+          home: AppLifecycleService.instance.mediaExtensionAction.action ==
+                  IntentAction.view
+              ? const FileViewer()
+              : const HomeWidget(),
+          debugShowCheckedModeBanner: false,
+          builder: EasyLoading.init(),
+          supportedLocales: AppLocalizations.supportedLocales,
+          localizationsDelegates: AppLocalizations.localizationsDelegates,
+        ),
       );
     } else {
       return MaterialApp(

+ 6 - 2
lib/db/files_db.dart

@@ -1091,13 +1091,17 @@ class FilesDB {
     return count ?? 0;
   }
 
-  Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
+  Future<int> archivedFilesCount(
+    int visibility,
+    int ownerID,
+    Set<int> hiddenCollections,
+  ) async {
     final db = await instance.database;
     final count = Sqflite.firstIntValue(
       await db.rawQuery(
         'SELECT COUNT(distinct($columnUploadedFileID)) FROM $filesTable where '
         '$columnMMdVisibility'
-        ' = $visibility AND $columnOwnerID = $ownerID',
+        ' = $visibility AND $columnOwnerID = $ownerID AND $columnCollectionID NOT IN (${hiddenCollections.join(', ')})',
       ),
     );
     return count ?? 0;

+ 3 - 1
lib/extensions/input_formatter.dart

@@ -3,7 +3,9 @@ import "package:flutter/services.dart";
 class UpperCaseTextFormatter extends TextInputFormatter {
   @override
   TextEditingValue formatEditUpdate(
-      TextEditingValue oldValue, TextEditingValue newValue) {
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  ) {
     return TextEditingValue(
       text: newValue.text.toUpperCase(),
       selection: newValue.selection,

+ 2 - 0
lib/main.dart

@@ -5,6 +5,7 @@ import 'package:background_fetch/background_fetch.dart';
 import 'package:firebase_messaging/firebase_messaging.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import "package:flutter/rendering.dart";
 import 'package:logging/logging.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:photos/app.dart';
@@ -54,6 +55,7 @@ const kFGTaskDeathTimeoutInMicroseconds = 5000000;
 const kBackgroundLockLatency = Duration(seconds: 3);
 
 void main() async {
+  debugRepaintRainbowEnabled = false;
   WidgetsFlutterBinding.ensureInitialized();
   await _runInForeground();
   BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);

+ 5 - 3
lib/services/app_lifecycle_service.dart

@@ -5,15 +5,17 @@ class AppLifecycleService {
   final _logger = Logger("AppLifecycleService");
 
   bool isForeground = false;
-  IntentAction intentAction = IntentAction.main;
+  MediaExtentionAction mediaExtensionAction =
+      MediaExtentionAction(action: IntentAction.main);
 
   static final AppLifecycleService instance =
       AppLifecycleService._privateConstructor();
 
   AppLifecycleService._privateConstructor();
 
-  void setIntentAction(IntentAction intentAction) {
-    this.intentAction = intentAction;
+  void setMediaExtensionAction(MediaExtentionAction mediaExtensionAction) {
+    _logger.info("App invoked via ${mediaExtensionAction.action}");
+    this.mediaExtensionAction = mediaExtensionAction;
   }
 
   void onAppInForeground(String reason) {

+ 3 - 1
lib/services/collections_service.dart

@@ -185,7 +185,9 @@ class CollectionsService {
   Future<List<CollectionWithThumbnail>> getArchivedCollectionWithThumb() async {
     final allCollections = await getCollectionsWithThumbnails();
     return allCollections
-        .where((element) => element.collection.isArchived())
+        .where(
+          (c) => c.collection.isArchived() && !c.collection.isHidden(),
+        )
         .toList();
   }
 

+ 13 - 0
lib/services/memories_service.dart

@@ -1,8 +1,10 @@
 import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/constants.dart';
+import "package:photos/core/event_bus.dart";
 import 'package:photos/db/files_db.dart';
 import 'package:photos/db/memories_db.dart';
+import "package:photos/events/files_updated_event.dart";
 import 'package:photos/models/filters/important_items_filter.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/collections_service.dart';
@@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
         DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay),
       );
     });
+    Bus.instance.on<FilesUpdatedEvent>().where((event) {
+      return event.type == EventType.deletedFromEverywhere;
+    }).listen((event) {
+      final generatedIDs = event.updatedFiles
+          .where((element) => element.generatedID != null)
+          .map((e) => e.generatedID!)
+          .toSet();
+      _cachedMemories?.removeWhere((element) {
+        return generatedIDs.contains(element.file.generatedID);
+      });
+    });
   }
 
   void clearCache() {

+ 7 - 3
lib/services/storage_bonus_service.dart

@@ -6,7 +6,8 @@ class StorageBonusService {
   late StorageBonusGateway gateway;
   late SharedPreferences prefs;
 
-  final String _showStorageBonus = "showStorageBonus.showBanner";
+  final int minTapCountBeforeHidingBanner = 5;
+  final String _showStorageBonusTapCount = "showStorageBonus.tap_count";
 
   void init(SharedPreferences preferences) {
     prefs = preferences;
@@ -18,12 +19,15 @@ class StorageBonusService {
   static StorageBonusService instance =
       StorageBonusService._privateConstructor();
 
+  // returns true if _showStorageBonusTapCount value is less than minTapCountBeforeHidingBanner
   bool shouldShowStorageBonus() {
-    return prefs.getBool(_showStorageBonus) ?? true;
+    final tapCount = prefs.getInt(_showStorageBonusTapCount) ?? 0;
+    return tapCount <= minTapCountBeforeHidingBanner;
   }
 
   void markStorageBonusAsDone() {
-    prefs.setBool(_showStorageBonus, false).ignore();
+    final tapCount = prefs.getInt(_showStorageBonusTapCount) ?? 0;
+    prefs.setInt(_showStorageBonusTapCount, tapCount + 1).ignore();
   }
 
   // getter for gateway

+ 1 - 1
lib/services/sync_service.dart

@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:io';
 
-import 'package:connectivity/connectivity.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:dio/dio.dart';
 import 'package:logging/logging.dart';
 import 'package:photo_manager/photo_manager.dart';

+ 9 - 0
lib/theme/colors.dart

@@ -48,6 +48,10 @@ class EnteColorScheme {
   final Color warning800;
   final Color caution500;
 
+  //golden colors
+  final Color golden700;
+  final Color golden500;
+
   //other colors
   final Color tabIcon;
   final List<Color> avatarColors;
@@ -86,6 +90,8 @@ class EnteColorScheme {
     this.warning500 = _warning500,
     this.warning400 = _warning400,
     this.caution500 = _caution500,
+    this.golden700 = _golden700,
+    this.golden500 = _golden500,
   });
 }
 
@@ -224,6 +230,9 @@ const Color _warning800 = Color(0xFFF53434);
 
 const Color _caution500 = Color.fromRGBO(255, 194, 71, 1);
 
+const Color _golden700 = Color(0xFFFDB816);
+const Color _golden500 = Color(0xFFFFC336);
+
 const List<Color> avatarLight = [
   Color.fromRGBO(118, 84, 154, 1),
   Color.fromRGBO(223, 120, 97, 1),

+ 68 - 3
lib/theme/text_style.dart

@@ -86,6 +86,26 @@ class EnteTextTheme {
   final TextStyle brandSmall;
   final TextStyle brandMedium;
 
+  // textMuted variants
+  final TextStyle h1Muted;
+  final TextStyle h2Muted;
+  final TextStyle h3Muted;
+  final TextStyle largeMuted;
+  final TextStyle bodyMuted;
+  final TextStyle smallMuted;
+  final TextStyle miniMuted;
+  final TextStyle tinyMuted;
+
+  // textFaint variants
+  final TextStyle h1Faint;
+  final TextStyle h2Faint;
+  final TextStyle h3Faint;
+  final TextStyle largeFaint;
+  final TextStyle bodyFaint;
+  final TextStyle smallFaint;
+  final TextStyle miniFaint;
+  final TextStyle tinyFaint;
+
   const EnteTextTheme({
     required this.h1,
     required this.h1Bold,
@@ -105,13 +125,42 @@ class EnteTextTheme {
     required this.tinyBold,
     required this.brandSmall,
     required this.brandMedium,
+    required this.h1Muted,
+    required this.h2Muted,
+    required this.h3Muted,
+    required this.largeMuted,
+    required this.bodyMuted,
+    required this.smallMuted,
+    required this.miniMuted,
+    required this.tinyMuted,
+    required this.h1Faint,
+    required this.h2Faint,
+    required this.h3Faint,
+    required this.largeFaint,
+    required this.bodyFaint,
+    required this.smallFaint,
+    required this.miniFaint,
+    required this.tinyFaint,
   });
 }
 
-EnteTextTheme lightTextTheme = _buildEnteTextStyle(textBaseLight);
-EnteTextTheme darkTextTheme = _buildEnteTextStyle(textBaseDark);
+EnteTextTheme lightTextTheme = _buildEnteTextStyle(
+  textBaseLight,
+  textMutedLight,
+  textFaintLight,
+);
+
+EnteTextTheme darkTextTheme = _buildEnteTextStyle(
+  textBaseDark,
+  textMutedDark,
+  textFaintDark,
+);
 
-EnteTextTheme _buildEnteTextStyle(Color color) {
+EnteTextTheme _buildEnteTextStyle(
+  Color color,
+  Color textMuted,
+  Color textFaint,
+) {
   return EnteTextTheme(
     h1: h1.copyWith(color: color),
     h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight),
@@ -131,5 +180,21 @@ EnteTextTheme _buildEnteTextStyle(Color color) {
     tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight),
     brandSmall: brandStyleSmall.copyWith(color: color),
     brandMedium: brandStyleMedium.copyWith(color: color),
+    h1Muted: h1.copyWith(color: textMuted),
+    h2Muted: h2.copyWith(color: textMuted),
+    h3Muted: h3.copyWith(color: textMuted),
+    largeMuted: large.copyWith(color: textMuted),
+    bodyMuted: body.copyWith(color: textMuted),
+    smallMuted: small.copyWith(color: textMuted),
+    miniMuted: mini.copyWith(color: textMuted),
+    tinyMuted: tiny.copyWith(color: textMuted),
+    h1Faint: h1.copyWith(color: textFaint),
+    h2Faint: h2.copyWith(color: textFaint),
+    h3Faint: h3.copyWith(color: textFaint),
+    largeFaint: large.copyWith(color: textFaint),
+    bodyFaint: body.copyWith(color: textFaint),
+    smallFaint: small.copyWith(color: textFaint),
+    miniFaint: mini.copyWith(color: textFaint),
+    tinyFaint: tiny.copyWith(color: textFaint),
   );
 }

+ 143 - 0
lib/ui/actions/file/file_actions.dart

@@ -0,0 +1,143 @@
+import "package:flutter/cupertino.dart";
+import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
+import "package:photos/models/file.dart";
+import "package:photos/models/file_type.dart";
+import "package:photos/theme/colors.dart";
+import "package:photos/theme/ente_theme.dart";
+import "package:photos/ui/components/action_sheet_widget.dart";
+import "package:photos/ui/components/button_widget.dart";
+import "package:photos/ui/components/models/button_type.dart";
+import "package:photos/ui/viewer/file/file_info_widget.dart";
+import "package:photos/utils/delete_file_util.dart";
+import "package:photos/utils/dialog_util.dart";
+import "package:photos/utils/toast_util.dart";
+
+Future<void> showSingleFileDeleteSheet(
+  BuildContext context,
+  File file, {
+  Function(File)? onFileRemoved,
+}) async {
+  final List<ButtonWidget> buttons = [];
+  final String fileType = file.fileType == FileType.video ? "video" : "photo";
+  final bool isBothLocalAndRemote =
+      file.uploadedFileID != null && file.localID != null;
+  final bool isLocalOnly = file.uploadedFileID == null && file.localID != null;
+  final bool isRemoteOnly = file.uploadedFileID != null && file.localID == null;
+  const String bodyHighlight = "It will be deleted from all albums.";
+  String body = "";
+  if (isBothLocalAndRemote) {
+    body = "This $fileType is in both ente and your device.";
+  } else if (isRemoteOnly) {
+    body = "This $fileType will be deleted from ente.";
+  } else if (isLocalOnly) {
+    body = "This $fileType will be deleted from your device.";
+  } else {
+    throw AssertionError("Unexpected state");
+  }
+  // Add option to delete from ente
+  if (isBothLocalAndRemote || isRemoteOnly) {
+    buttons.add(
+      ButtonWidget(
+        labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.first,
+        shouldSurfaceExecutionStates: true,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesFromRemoteOnly(context, [file]);
+          showShortToast(context, "Moved to trash");
+          if (isRemoteOnly) {
+            Navigator.of(context, rootNavigator: true).pop();
+            if (onFileRemoved != null) {
+              onFileRemoved(file);
+            }
+          }
+        },
+      ),
+    );
+  }
+  // Add option to delete from local
+  if (isBothLocalAndRemote || isLocalOnly) {
+    buttons.add(
+      ButtonWidget(
+        labelText: isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.second,
+        shouldSurfaceExecutionStates: false,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesOnDeviceOnly(context, [file]);
+          if (isLocalOnly) {
+            Navigator.of(context, rootNavigator: true).pop();
+            if (onFileRemoved != null) {
+              onFileRemoved(file);
+            }
+          }
+        },
+      ),
+    );
+  }
+  if (isBothLocalAndRemote) {
+    buttons.add(
+      ButtonWidget(
+        labelText: "Delete from both",
+        buttonType: ButtonType.neutral,
+        buttonSize: ButtonSize.large,
+        shouldStickToDarkTheme: true,
+        buttonAction: ButtonAction.third,
+        shouldSurfaceExecutionStates: true,
+        isInAlert: true,
+        onTap: () async {
+          await deleteFilesFromEverywhere(context, [file]);
+          Navigator.of(context, rootNavigator: true).pop();
+          if (onFileRemoved != null) {
+            onFileRemoved(file);
+          }
+        },
+      ),
+    );
+  }
+  buttons.add(
+    const ButtonWidget(
+      labelText: "Cancel",
+      buttonType: ButtonType.secondary,
+      buttonSize: ButtonSize.large,
+      shouldStickToDarkTheme: true,
+      buttonAction: ButtonAction.fourth,
+      isInAlert: true,
+    ),
+  );
+  final actionResult = await showActionSheet(
+    context: context,
+    buttons: buttons,
+    actionSheetType: ActionSheetType.defaultActionSheet,
+    body: body,
+    bodyHighlight: bodyHighlight,
+  );
+  if (actionResult?.action != null &&
+      actionResult!.action == ButtonAction.error) {
+    showGenericErrorDialog(context: context);
+  }
+}
+
+Future<void> showInfoSheet(BuildContext context, File file) async {
+  final colorScheme = getEnteColorScheme(context);
+  return showBarModalBottomSheet(
+    topControl: const SizedBox.shrink(),
+    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
+    backgroundColor: colorScheme.backgroundElevated,
+    barrierColor: backdropFaintDark,
+    context: context,
+    builder: (BuildContext context) {
+      return Padding(
+        padding:
+            EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
+        child: FileInfoWidget(file),
+      );
+    },
+  );
+}

+ 5 - 2
lib/ui/collection_action_sheet.dart

@@ -597,8 +597,11 @@ class _CollectionActionSheetState extends State<CollectionActionSheet> {
   }
 
   Future<bool> _restoreFilesToCollection(int toCollectionID) async {
-    final dialog = createProgressDialog(context, "Restoring files...",
-        isDismissible: true);
+    final dialog = createProgressDialog(
+      context,
+      "Restoring files...",
+      isDismissible: true,
+    );
     await dialog.show();
     try {
       await CollectionsService.instance

+ 5 - 1
lib/ui/collections/archived_collections_button_widget.dart

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/models/magic_metadata.dart';
+import "package:photos/services/collections_service.dart";
 import 'package:photos/ui/viewer/gallery/archive_page.dart';
 import 'package:photos/utils/navigation_util.dart';
 
@@ -15,6 +16,8 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
+    final Set<int> hiddenCollectionId =
+        CollectionsService.instance.getHiddenCollections();
     return OutlinedButton(
       style: OutlinedButton.styleFrom(
         backgroundColor: Theme.of(context).backgroundColor,
@@ -43,9 +46,10 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
                   ),
                   const Padding(padding: EdgeInsets.all(6)),
                   FutureBuilder<int>(
-                    future: FilesDB.instance.fileCountWithVisibility(
+                    future: FilesDB.instance.archivedFilesCount(
                       visibilityArchive,
                       Configuration.instance.getUserID()!,
+                      hiddenCollectionId,
                     ),
                     builder: (context, snapshot) {
                       if (snapshot.hasData && snapshot.data! > 0) {

+ 100 - 0
lib/ui/components/album_horizontal_list_widget.dart

@@ -0,0 +1,100 @@
+import "dart:async";
+
+import "package:flutter/cupertino.dart";
+import "package:logging/logging.dart";
+import "package:photos/core/event_bus.dart";
+import "package:photos/events/collection_updated_event.dart";
+import "package:photos/models/collection_items.dart";
+import "package:photos/ui/collections/collection_item_widget.dart";
+import "package:photos/ui/common/loading_widget.dart";
+import "package:photos/ui/components/divider_widget.dart";
+
+class AlbumHorizontalListWidget extends StatefulWidget {
+  final Future<List<CollectionWithThumbnail>> Function() collectionsFuture;
+
+  const AlbumHorizontalListWidget(
+    this.collectionsFuture, {
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  State<AlbumHorizontalListWidget> createState() =>
+      _AlbumHorizontalListWidgetState();
+}
+
+class _AlbumHorizontalListWidgetState extends State<AlbumHorizontalListWidget> {
+  late StreamSubscription<CollectionUpdatedEvent>
+      _collectionUpdatesSubscription;
+  late Logger _logger;
+
+  @override
+  void initState() {
+    _collectionUpdatesSubscription =
+        Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
+      setState(() {});
+    });
+    _logger = Logger((_AlbumHorizontalListWidgetState).toString());
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    _collectionUpdatesSubscription.cancel();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    debugPrint('$runtimeType widget build');
+    return FutureBuilder<List<CollectionWithThumbnail>>(
+      future: widget.collectionsFuture(),
+      builder: (context, snapshot) {
+        if (snapshot.hasError) {
+          _logger.severe("failed to fetch albums", snapshot.error);
+          return const Text("Something went wrong");
+        } else if (snapshot.hasData) {
+          if (snapshot.data!.isEmpty) {
+            return const SizedBox.shrink();
+          }
+          final collectionsWithThumbnail =
+              snapshot.data as List<CollectionWithThumbnail>;
+          return Align(
+            alignment: Alignment.centerLeft,
+            child: SizedBox(
+              height: 190,
+              child: Column(
+                children: [
+                  Expanded(
+                    child: ListView.builder(
+                      scrollDirection: Axis.horizontal,
+                      itemCount: collectionsWithThumbnail.length,
+                      padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
+                      itemBuilder: (context, index) {
+                        final item = collectionsWithThumbnail[index];
+                        return GestureDetector(
+                          behavior: HitTestBehavior.opaque,
+                          onTap: () async {},
+                          child: Padding(
+                            padding: const EdgeInsets.all(2.0),
+                            child: CollectionItem(
+                              item,
+                              120,
+                              shouldRender: true,
+                            ),
+                          ),
+                        );
+                      },
+                    ),
+                  ),
+                  const DividerWidget(dividerType: DividerType.solid),
+                ],
+              ),
+            ),
+          );
+        } else {
+          return const EnteLoadingWidget();
+        }
+      },
+    );
+  }
+}

+ 13 - 1
lib/ui/components/notification_widget.dart

@@ -1,6 +1,7 @@
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/theme/colors.dart';
+import "package:photos/theme/ente_theme.dart";
 import 'package:photos/theme/text_style.dart';
 import 'package:photos/ui/components/icon_button_widget.dart';
 
@@ -8,6 +9,7 @@ import 'package:photos/ui/components/icon_button_widget.dart';
 enum NotificationType {
   warning,
   banner,
+  goldenBanner,
 }
 
 class NotificationWidget extends StatelessWidget {
@@ -30,7 +32,9 @@ class NotificationWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    Color backgroundColor = Colors.white;
+    final colorScheme = getEnteColorScheme(context);
+    LinearGradient? backgroundGradient;
+    Color? backgroundColor;
     switch (type) {
       case NotificationType.warning:
         backgroundColor = warning500;
@@ -38,6 +42,13 @@ class NotificationWidget extends StatelessWidget {
       case NotificationType.banner:
         backgroundColor = backgroundElevated2Dark;
         break;
+      case NotificationType.goldenBanner:
+        backgroundGradient = LinearGradient(
+          colors: [colorScheme.golden700, colorScheme.golden500],
+          stops: const [0.25, 1],
+          begin: Alignment.bottomCenter,
+          end: Alignment.topCenter,
+        );
     }
     return Center(
       child: GestureDetector(
@@ -49,6 +60,7 @@ class NotificationWidget extends StatelessWidget {
             ),
             boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu,
             color: backgroundColor,
+            gradient: backgroundGradient,
           ),
           child: Padding(
             padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),

+ 23 - 5
lib/ui/growth/apply_code_screen.dart

@@ -1,6 +1,8 @@
 import "package:flutter/material.dart";
 import "package:logging/logging.dart";
 import "package:photos/extensions/input_formatter.dart";
+import "package:photos/models/api/storage_bonus/storage_bonus.dart";
+import "package:photos/models/user_details.dart";
 import "package:photos/services/storage_bonus_service.dart";
 import "package:photos/theme/ente_theme.dart";
 import "package:photos/ui/components/button_widget.dart";
@@ -8,10 +10,18 @@ import "package:photos/ui/components/icon_button_widget.dart";
 import "package:photos/ui/components/models/button_type.dart";
 import "package:photos/ui/components/title_bar_title_widget.dart";
 import "package:photos/ui/components/title_bar_widget.dart";
+import "package:photos/ui/growth/code_success_screen.dart";
 import "package:photos/utils/dialog_util.dart";
 
 class ApplyCodeScreen extends StatefulWidget {
-  const ApplyCodeScreen({super.key});
+  // referrerView and userDetails used to render code_success_screen
+  final ReferralView referralView;
+  final UserDetails userDetails;
+  const ApplyCodeScreen(
+    this.referralView,
+    this.userDetails, {
+    super.key,
+  });
 
   @override
   State<ApplyCodeScreen> createState() => _ApplyCodeScreenState();
@@ -112,14 +122,22 @@ class _ApplyCodeScreenState extends State<ApplyCodeScreen> {
                           await StorageBonusService.instance
                               .getGateway()
                               .claimReferralCode(code.trim().toUpperCase());
-                          Navigator.of(context).pop();
+
+                          Navigator.of(context).pushReplacement(
+                            MaterialPageRoute(
+                              builder: (context) => CodeSuccessScreen(
+                                widget.referralView,
+                                widget.userDetails,
+                              ),
+                            ),
+                          );
                         } catch (e) {
                           Logger('$runtimeType')
                               .severe("failed to apply referral", e);
                           showErrorDialogForException(
-                            context: context,
-                            exception: e as Exception,
-                          );
+                              context: context,
+                              exception: e as Exception,
+                              apiErrorPrefix: "Failed to apply code");
                         }
                       },
                     )

+ 163 - 0
lib/ui/growth/code_success_screen.dart

@@ -0,0 +1,163 @@
+import "package:flutter/material.dart";
+import "package:flutter_animate/flutter_animate.dart";
+import "package:photos/models/api/storage_bonus/storage_bonus.dart";
+import "package:photos/models/user_details.dart";
+import "package:photos/theme/ente_theme.dart";
+import "package:photos/ui/components/captioned_text_widget.dart";
+import "package:photos/ui/components/icon_button_widget.dart";
+import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
+import "package:photos/ui/components/title_bar_title_widget.dart";
+import "package:photos/ui/components/title_bar_widget.dart";
+import "package:photos/ui/growth/referral_code_widget.dart";
+import "package:photos/ui/growth/storage_details_screen.dart";
+import "package:photos/utils/navigation_util.dart";
+import "package:photos/utils/share_util.dart";
+
+class CodeSuccessScreen extends StatelessWidget {
+  final ReferralView referralView;
+  final UserDetails userDetails;
+
+  const CodeSuccessScreen(this.referralView, this.userDetails, {super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final colorScheme = getEnteColorScheme(context);
+    final textStyle = getEnteTextTheme(context);
+    return Scaffold(
+      body: CustomScrollView(
+        primary: false,
+        slivers: <Widget>[
+          TitleBarWidget(
+            flexibleSpaceTitle: const TitleBarTitleWidget(
+              title: "Code applied",
+            ),
+            actionIcons: [
+              IconButtonWidget(
+                icon: Icons.close_outlined,
+                iconButtonType: IconButtonType.secondary,
+                onTap: () {
+                  Navigator.pop(context);
+                },
+              ),
+            ],
+          ),
+          SliverList(
+            delegate: SliverChildBuilderDelegate(
+              (delegateBuildContext, index) {
+                return Padding(
+                  padding: const EdgeInsets.symmetric(
+                    horizontal: 12,
+                    vertical: 6,
+                  ),
+                  child: Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 12.0),
+                    child: Column(
+                      crossAxisAlignment: CrossAxisAlignment.center,
+                      children: [
+                        Icon(
+                          Icons.check,
+                          color: colorScheme.primary500,
+                          size: 96,
+                        )
+                            .animate()
+                            .scaleXY(
+                              begin: 0.5,
+                              end: 1,
+                              duration: 750.ms,
+                              curve: Curves.easeInOutCubic,
+                              delay: 250.ms,
+                            )
+                            .fadeIn(
+                              duration: 500.ms,
+                              curve: Curves.easeInOutCubic,
+                            ),
+                        Text(
+                          "${referralView.planInfo.storageInGB} GB",
+                          style: textStyle.h2Bold,
+                        ),
+                        Text(
+                          "Claimed",
+                          style: textStyle.body
+                              .copyWith(color: colorScheme.textMuted),
+                        ),
+                        const SizedBox(height: 32),
+                        MenuItemWidget(
+                          captionedTextWidget: const CaptionedTextWidget(
+                            title: "Details",
+                          ),
+                          menuItemColor: colorScheme.fillFaint,
+                          trailingWidget: Icon(
+                            Icons.chevron_right_outlined,
+                            color: colorScheme.strokeBase,
+                          ),
+                          singleBorderRadius: 8,
+                          alignCaptionedTextToLeft: true,
+                          onTap: () async {
+                            routeToPage(
+                              context,
+                              StorageDetailsScreen(referralView, userDetails),
+                            );
+                          },
+                        ),
+                        const SizedBox(height: 32),
+                        InkWell(
+                          onTap: () {
+                            shareText(
+                              "ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get ${referralView.planInfo.storageInGB} GB free after you signup for a paid plan\n\nhttps://ente.io",
+                            );
+                          },
+                          child: Container(
+                            width: double.infinity,
+                            decoration: BoxDecoration(
+                              border: Border.all(
+                                color: colorScheme.strokeFaint,
+                                width: 1,
+                              ),
+                              borderRadius: BorderRadius.circular(8),
+                            ),
+                            child: Padding(
+                              padding: const EdgeInsets.symmetric(
+                                vertical: 12,
+                                horizontal: 12,
+                              ),
+                              child: Column(
+                                crossAxisAlignment: CrossAxisAlignment.center,
+                                children: [
+                                  Text(
+                                    "Claim more!",
+                                    style: textStyle.body,
+                                  ),
+                                  const SizedBox(height: 8),
+                                  Text(
+                                    "${referralView.planInfo.storageInGB} GB each time someone signs up for a paid plan and applies your code",
+                                    style: textStyle.small
+                                        .copyWith(color: colorScheme.textMuted),
+                                    textAlign: TextAlign.center,
+                                  ),
+                                  const SizedBox(height: 16),
+                                  ReferralCodeWidget(referralView.code),
+                                  const SizedBox(height: 16),
+                                  Text(
+                                    "They also get ${referralView.planInfo.storageInGB} GB",
+                                    style: textStyle.small
+                                        .copyWith(color: colorScheme.textMuted),
+                                    textAlign: TextAlign.center,
+                                  ),
+                                ],
+                              ),
+                            ),
+                          ),
+                        )
+                      ],
+                    ),
+                  ),
+                );
+              },
+              childCount: 1,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 52 - 0
lib/ui/growth/referral_code_widget.dart

@@ -0,0 +1,52 @@
+import "package:dotted_border/dotted_border.dart";
+import "package:flutter/material.dart";
+import "package:photos/theme/ente_theme.dart";
+
+// Figma: https://www.figma.com/file/SYtMyLBs5SAOkTbfMMzhqt/ente-Visual-Design?node-id=11219%3A62974&t=BRCLJhxXP11Q3Wyw-0
+class ReferralCodeWidget extends StatelessWidget {
+  final String codeValue;
+
+  const ReferralCodeWidget(this.codeValue, {Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    final colorScheme = getEnteColorScheme(context);
+    final textStyle = getEnteTextTheme(context);
+    return Center(
+      child: Container(
+        color: colorScheme.backgroundElevated2,
+        child: DottedBorder(
+          color: colorScheme.strokeMuted,
+          strokeWidth: 1,
+          dashPattern: const [6, 6],
+          radius: const Radius.circular(8),
+          child: Padding(
+            padding: const EdgeInsets.only(
+              left: 26.0,
+              top: 14,
+              right: 12,
+              bottom: 14,
+            ),
+            child: Row(
+              mainAxisSize: MainAxisSize.min,
+              children: [
+                Text(
+                  codeValue,
+                  style: textStyle.bodyBold.copyWith(
+                    color: colorScheme.primary700,
+                  ),
+                ),
+                const SizedBox(width: 12),
+                Icon(
+                  Icons.adaptive.share,
+                  size: 22,
+                  color: colorScheme.strokeMuted,
+                )
+              ],
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 16 - 44
lib/ui/growth/referral_screen.dart

@@ -1,4 +1,3 @@
-import "package:dotted_border/dotted_border.dart";
 import "package:flutter/material.dart";
 import "package:photos/models/api/storage_bonus/storage_bonus.dart";
 import "package:photos/models/user_details.dart";
@@ -14,6 +13,7 @@ import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
 import "package:photos/ui/components/title_bar_title_widget.dart";
 import "package:photos/ui/components/title_bar_widget.dart";
 import "package:photos/ui/growth/apply_code_screen.dart";
+import "package:photos/ui/growth/referral_code_widget.dart";
 import "package:photos/ui/growth/storage_details_screen.dart";
 import "package:photos/utils/data_util.dart";
 import "package:photos/utils/navigation_util.dart";
@@ -129,7 +129,8 @@ class ReferralWidget extends StatelessWidget {
             ? InkWell(
                 onTap: () {
                   shareText(
-                      "ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get 10 GB free after you signup for a paid plan\n\nhttps://ente.io");
+                    "ente referral code: ${referralView.code} \n\nApply it in Settings → General → Referrals to get 10 GB free after you signup for a paid plan\n\nhttps://ente.io",
+                  );
                 },
                 child: Container(
                   width: double.infinity,
@@ -153,41 +154,7 @@ class ReferralWidget extends StatelessWidget {
                           "friends",
                         ),
                         const SizedBox(height: 12),
-                        Center(
-                          child: DottedBorder(
-                            color: colorScheme.strokeMuted,
-                            //color of dotted/dash line
-                            strokeWidth: 1,
-                            //thickness of dash/dots
-                            dashPattern: const [6, 6],
-                            radius: const Radius.circular(8),
-                            child: Padding(
-                              padding: const EdgeInsets.only(
-                                left: 26.0,
-                                top: 14,
-                                right: 12,
-                                bottom: 14,
-                              ),
-                              child: Row(
-                                mainAxisSize: MainAxisSize.min,
-                                children: [
-                                  Text(
-                                    referralView.code,
-                                    style: textStyle.bodyBold.copyWith(
-                                      color: colorScheme.primary700,
-                                    ),
-                                  ),
-                                  const SizedBox(width: 12),
-                                  Icon(
-                                    Icons.adaptive.share,
-                                    size: 22,
-                                    color: colorScheme.strokeMuted,
-                                  )
-                                ],
-                              ),
-                            ),
-                          ),
-                        ),
+                        ReferralCodeWidget(referralView.code),
                         const SizedBox(height: 12),
                         const Text(
                           "2. They sign up for a paid plan",
@@ -213,9 +180,11 @@ class ReferralWidget extends StatelessWidget {
                         color: colorScheme.strokeMuted,
                       ),
                       const SizedBox(height: 12),
-                      Text("Referrals are currently paused",
-                          style: textStyle.small
-                              .copyWith(color: colorScheme.textFaint)),
+                      Text(
+                        "Referrals are currently paused",
+                        style: textStyle.small
+                            .copyWith(color: colorScheme.textFaint),
+                      ),
                     ],
                   ),
                 ),
@@ -247,7 +216,7 @@ class ReferralWidget extends StatelessWidget {
                 onTap: () async {
                   await routeToPage(
                     context,
-                    const ApplyCodeScreen(),
+                    ApplyCodeScreen(referralView, userDetails),
                   );
                   notifyParent();
                 },
@@ -273,9 +242,12 @@ class ReferralWidget extends StatelessWidget {
           alignCaptionedTextToLeft: true,
           onTap: () async {
             routeToPage(
-                context,
-                const WebPage(
-                    "FAQ", "https://ente.io/faq/general/referral-program"));
+              context,
+              const WebPage(
+                "FAQ",
+                "https://ente.io/faq/general/referral-program",
+              ),
+            );
           },
         ),
         const SizedBox(height: 24),

+ 10 - 6
lib/ui/growth/storage_details_screen.dart

@@ -108,19 +108,23 @@ class _StorageDetailsScreenState extends State<StorageDetailsScreen> {
                                 BonusInfoSection(
                                   sectionName: "Free storage claimed",
                                   leftValue: convertBytesToAbsoluteGBs(
-                                      widget.referralView.claimedStorage),
+                                    widget.referralView.claimedStorage,
+                                  ),
                                   leftUnitName: "GB",
                                   rightValue: null,
                                 ),
                                 BonusInfoSection(
                                   sectionName: "Free storage usable",
-                                  leftValue: convertBytesToAbsoluteGBs(min(
-                                    widget.referralView.claimedStorage,
-                                    widget.userDetails.getTotalStorage(),
-                                  )),
+                                  leftValue: convertBytesToAbsoluteGBs(
+                                    min(
+                                      widget.referralView.claimedStorage,
+                                      widget.userDetails.getTotalStorage(),
+                                    ),
+                                  ),
                                   leftUnitName: "GB",
                                   rightValue: convertBytesToAbsoluteGBs(
-                                      widget.userDetails.getTotalStorage()),
+                                    widget.userDetails.getTotalStorage(),
+                                  ),
                                   rightUnitName: "GB",
                                 ),
                                 const SizedBox(

+ 62 - 13
lib/ui/home/memories_widget.dart

@@ -1,8 +1,11 @@
+import "dart:io";
+
+import "package:flutter/cupertino.dart";
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/memories_service.dart';
-import 'package:photos/ui/extents_page_view.dart';
+import "package:photos/ui/actions/file/file_actions.dart";
 import 'package:photos/ui/viewer/file/file_widget.dart';
 import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
 import 'package:photos/utils/date_time_util.dart';
@@ -315,6 +318,22 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
     );
   }
 
+  void onFileDeleted() {
+    if (widget.memories.length == 1) {
+      Navigator.pop(context);
+    } else {
+      setState(() {
+        if (_index != 0) {
+          _pageController?.jumpToPage(_index - 1);
+        }
+        widget.memories.removeAt(_index);
+        if (_index != 0) {
+          _index--;
+        }
+      });
+    }
+  }
+
   Hero _buildInfoText() {
     return Hero(
       tag: widget.title,
@@ -346,17 +365,48 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
 
   Widget _buildBottomIcons() {
     final file = widget.memories[_index].file;
-    return Container(
-      alignment: Alignment.bottomRight,
-      padding: const EdgeInsets.fromLTRB(0, 0, 26, 20),
-      child: IconButton(
-        icon: Icon(
-          Icons.adaptive.share,
-          color: Colors.white, //same for both themes
+    return SafeArea(
+      child: Container(
+        alignment: Alignment.bottomRight,
+        padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
+        child: Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          children: [
+            IconButton(
+              icon: Icon(
+                Platform.isAndroid ? Icons.info_outline : CupertinoIcons.info,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () {
+                showInfoSheet(context, file);
+              },
+            ),
+            IconButton(
+              icon: Icon(
+                Platform.isAndroid
+                    ? Icons.delete_outline
+                    : CupertinoIcons.delete,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () async {
+                await showSingleFileDeleteSheet(
+                  context,
+                  file,
+                  onFileRemoved: (file) => {onFileDeleted()},
+                );
+              },
+            ),
+            IconButton(
+              icon: Icon(
+                Icons.adaptive.share,
+                color: Colors.white, //same for both themes
+              ),
+              onPressed: () {
+                share(context, [file]);
+              },
+            ),
+          ],
         ),
-        onPressed: () {
-          share(context, [file]);
-        },
       ),
     );
   }
@@ -381,7 +431,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
 
   Widget _buildSwiper() {
     _pageController = PageController(initialPage: _index);
-    return ExtentsPageView.extents(
+    return PageView.builder(
       itemBuilder: (BuildContext context, int index) {
         if (index < widget.memories.length - 1) {
           final nextFile = widget.memories[index + 1].file;
@@ -405,7 +455,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
       },
       itemCount: widget.memories.length,
       controller: _pageController,
-      extents: 1,
       onPageChanged: (index) async {
         await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
         if (mounted) {

+ 2 - 4
lib/ui/home_widget.dart

@@ -265,7 +265,7 @@ class _HomeWidgetState extends State<HomeWidget> {
     _logger.info("Building home_Widget with tab $_selectedTabIndex");
     bool isSettingsOpen = false;
     final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport();
-
+    final action = AppLifecycleService.instance.mediaExtensionAction.action;
     return UserDetailsStateWidget(
       child: WillPopScope(
         child: Scaffold(
@@ -298,9 +298,7 @@ class _HomeWidgetState extends State<HomeWidget> {
               Navigator.pop(context);
               return false;
             }
-            if (Platform.isAndroid &&
-                AppLifecycleService.instance.intentAction ==
-                    IntentAction.main) {
+            if (Platform.isAndroid && action == IntentAction.main) {
               MoveToBackground.moveTaskToBack();
               return false;
             } else {

+ 5 - 4
lib/ui/huge_listview/lazy_loading_gallery.dart

@@ -311,7 +311,6 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
   bool? _shouldRender;
   int? _currentUserID;
   late StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent;
-  final _mediaExtensionPlugin = MediaExtension();
 
   @override
   void initState() {
@@ -426,16 +425,18 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
         if (widget.selectedFiles.files.isNotEmpty) {
           _selectFile(file);
         } else {
-          if (AppLifecycleService.instance.intentAction == IntentAction.pick) {
+          if (AppLifecycleService.instance.mediaExtensionAction.action ==
+              IntentAction.pick) {
             final ioFile = await getFile(file);
-            _mediaExtensionPlugin.setResult("file://${ioFile!.path}");
+            MediaExtension().setResult("file://${ioFile!.path}");
           } else {
             _routeToDetailPage(file, context);
           }
         }
       },
       onLongPress: () {
-        if (AppLifecycleService.instance.intentAction == IntentAction.main) {
+        if (AppLifecycleService.instance.mediaExtensionAction.action ==
+            IntentAction.main) {
           HapticFeedback.lightImpact();
           _selectFile(file);
         }

+ 20 - 13
lib/ui/settings_page.dart

@@ -2,6 +2,7 @@ import 'dart:io';
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
+import "package:flutter_animate/flutter_animate.dart";
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/events/opened_settings_event.dart';
@@ -77,19 +78,25 @@ class SettingsPage extends StatelessWidget {
       contents.addAll([
         const StorageCardWidget(),
         StorageBonusService.instance.shouldShowStorageBonus()
-            ? Padding(
-                padding: const EdgeInsets.symmetric(vertical: 8.0),
-                child: NotificationWidget(
-                  startIcon: Icons.auto_awesome,
-                  actionIcon: Icons.arrow_forward_outlined,
-                  text: "Double your storage",
-                  subText: "Refer friends and 2x your plan",
-                  type: NotificationType.banner,
-                  onTap: () async {
-                    StorageBonusService.instance.markStorageBonusAsDone();
-                    routeToPage(context, const ReferralScreen());
-                  },
-                ),
+            ? RepaintBoundary(
+                child: Padding(
+                  padding: const EdgeInsets.symmetric(vertical: 8.0),
+                  child: NotificationWidget(
+                    startIcon: Icons.auto_awesome,
+                    actionIcon: Icons.arrow_forward_outlined,
+                    text: "Double your storage",
+                    subText: "Refer friends and 2x your plan",
+                    type: NotificationType.goldenBanner,
+                    onTap: () async {
+                      StorageBonusService.instance.markStorageBonusAsDone();
+                      routeToPage(context, const ReferralScreen());
+                    },
+                  ),
+                ).animate(onPlay: (controller) => controller.repeat()).shimmer(
+                      duration: 1000.ms,
+                      delay: 3200.ms,
+                      size: 0.6,
+                    ),
               )
             : const SizedBox(height: 12),
         const BackupSectionWidget(),

+ 0 - 1
lib/ui/sharing/share_collection_page.dart

@@ -228,7 +228,6 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
           ),
           leadingIcon: Icons.link,
           menuItemColor: getEnteColorScheme(context).fillFaint,
-          isBottomBorderRadiusRemoved: true,
           showOnlyLoadingState: true,
           onTap: () async {
             final bool result =

+ 3 - 2
lib/ui/viewer/actions/delete_empty_albums.dart

@@ -100,8 +100,9 @@ class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
             "${collections.length}";
         try {
           await CollectionsService.instance.trashEmptyCollection(
-              collections[i].collection,
-              isBulkDelete: true);
+            collections[i].collection,
+            isBulkDelete: true,
+          );
         } catch (_) {
           failedCount++;
         }

+ 1 - 1
lib/ui/viewer/actions/file_selection_actions_widget.dart

@@ -51,7 +51,7 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
   late CollectionActions collectionActions;
   late bool isCollectionOwner;
 
-  // _cachedCollectionForSharedLink is primarly used to avoid creating duplicate
+  // _cachedCollectionForSharedLink is primarily used to avoid creating duplicate
   // links if user keeps on creating Create link button after selecting
   // few files. This link is reset on any selection changed;
   Collection? _cachedCollectionForSharedLink;

+ 104 - 0
lib/ui/viewer/actions/file_viewer.dart

@@ -0,0 +1,104 @@
+import 'dart:convert';
+
+import "package:chewie/chewie.dart";
+import "package:flutter/material.dart";
+import "package:flutter/services.dart";
+import "package:logging/logging.dart";
+import "package:media_extension/media_extension_action_types.dart";
+import "package:photo_view/photo_view.dart";
+import "package:photos/services/app_lifecycle_service.dart";
+
+import "package:video_player/video_player.dart";
+
+class FileViewer extends StatefulWidget {
+  const FileViewer({super.key});
+
+  @override
+  State<StatefulWidget> createState() {
+    return FileViewerState();
+  }
+}
+
+class FileViewerState extends State<FileViewer> {
+  final action = AppLifecycleService.instance.mediaExtensionAction;
+  ChewieController? controller;
+  VideoPlayerController? videoController;
+  final Logger _logger = Logger("FileViewer");
+
+  @override
+  void initState() {
+    super.initState();
+    if (action.type == MediaType.video) {
+      initController();
+    }
+  }
+
+  @override
+  void dispose() {
+    videoController?.dispose();
+    controller?.dispose();
+    super.dispose();
+  }
+
+  void initController() async {
+    videoController = VideoPlayerController.contentUri(
+      Uri.parse(action.data!),
+    );
+    controller = ChewieController(
+      videoPlayerController: videoController!,
+      autoInitialize: true,
+      aspectRatio: 16 / 9,
+      autoPlay: true,
+      looping: true,
+      showOptions: false,
+      materialProgressColors: ChewieProgressColors(
+        playedColor: const Color.fromRGBO(45, 194, 98, 1.0),
+        handleColor: Colors.white,
+        bufferedColor: Colors.white,
+      ),
+    );
+    controller!.addListener(() {
+      if (!controller!.isFullScreen) {
+        SystemChrome.setPreferredOrientations(
+          [DeviceOrientation.portraitUp],
+        );
+      }
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        leading: IconButton(
+          onPressed: () {
+            SystemChannels.platform.invokeMethod('SystemNavigator.pop');
+          },
+          icon: const Icon(Icons.arrow_back),
+        ),
+      ),
+      body: Column(
+        children: [
+          Expanded(
+            child: Center(
+              child: (() {
+                if (action.type == MediaType.image) {
+                  return PhotoView(
+                    imageProvider: MemoryImage(base64Decode(action.data!)),
+                  );
+                } else if (action.type == MediaType.video) {
+                  return controller != null
+                      ? Chewie(controller: controller!)
+                      : const CircularProgressIndicator();
+                } else {
+                  _logger.severe('unsupported file type ${action.type}');
+                  return const Icon(Icons.error);
+                }
+              })(),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 2 - 0
lib/ui/viewer/file/detail_page.dart

@@ -124,6 +124,8 @@ class _DetailPageState extends State<DetailPage> {
               _files![_selectedIndex],
               _onEditFileRequested,
               widget.config.mode == DetailPageMode.minimalistic,
+              onFileRemoved: _onFileRemoved,
+              userID: Configuration.instance.getUserID(),
               key: _bottomBarKey,
             ),
           ],

+ 17 - 112
lib/ui/viewer/file/fading_app_bar.dart

@@ -14,6 +14,7 @@ import 'package:photos/events/local_photos_updated_event.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/ignored_file.dart';
+import "package:photos/models/magic_metadata.dart";
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/models/trash_file.dart';
 import 'package:photos/services/collections_service.dart';
@@ -23,13 +24,10 @@ import 'package:photos/services/ignored_files_service.dart';
 import 'package:photos/services/local_sync_service.dart';
 import 'package:photos/ui/collection_action_sheet.dart';
 import 'package:photos/ui/common/progress_dialog.dart';
-import 'package:photos/ui/components/action_sheet_widget.dart';
-import 'package:photos/ui/components/button_widget.dart';
-import 'package:photos/ui/components/models/button_type.dart';
 import 'package:photos/ui/viewer/file/custom_app_bar.dart';
-import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/file_util.dart';
+import "package:photos/utils/magic_util.dart";
 import 'package:photos/utils/toast_util.dart';
 
 class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
@@ -148,22 +146,22 @@ class FadingAppBarState extends State<FadingAppBar> {
             );
           }
           // options for files owned by the user
-          if (isOwnedByUser) {
+          if (isOwnedByUser && !isFileHidden) {
+            final bool isArchived =
+                widget.file.magicMetadata.visibility == visibilityArchive;
             items.add(
               PopupMenuItem(
                 value: 2,
                 child: Row(
                   children: [
                     Icon(
-                      Platform.isAndroid
-                          ? Icons.delete_outline
-                          : CupertinoIcons.delete,
+                      isArchived ? Icons.unarchive : Icons.archive_outlined,
                       color: Theme.of(context).iconTheme.color,
                     ),
                     const Padding(
                       padding: EdgeInsets.all(8),
                     ),
-                    const Text("Delete"),
+                    Text(isArchived ? "Unarchive" : "Archive"),
                   ],
                 ),
               ),
@@ -235,7 +233,7 @@ class FadingAppBarState extends State<FadingAppBar> {
           if (value == 1) {
             _download(widget.file);
           } else if (value == 2) {
-            await _showSingleFileDeleteSheet(widget.file);
+            await _toggleFileArchiveStatus(widget.file);
           } else if (value == 3) {
             _setAs(widget.file);
           } else if (value == 4) {
@@ -337,109 +335,16 @@ class FadingAppBarState extends State<FadingAppBar> {
     );
   }
 
-  Future<void> _showSingleFileDeleteSheet(File file) async {
-    final List<ButtonWidget> buttons = [];
-    final String fileType = file.fileType == FileType.video ? "video" : "photo";
-    final bool isBothLocalAndRemote =
-        file.uploadedFileID != null && file.localID != null;
-    final bool isLocalOnly =
-        file.uploadedFileID == null && file.localID != null;
-    final bool isRemoteOnly =
-        file.uploadedFileID != null && file.localID == null;
-    const String bodyHighlight = "It will be deleted from all albums.";
-    String body = "";
-    if (isBothLocalAndRemote) {
-      body = "This $fileType is in both ente and your device.";
-    } else if (isRemoteOnly) {
-      body = "This $fileType will be deleted from ente.";
-    } else if (isLocalOnly) {
-      body = "This $fileType will be deleted from your device.";
-    } else {
-      throw AssertionError("Unexpected state");
-    }
-    // Add option to delete from ente
-    if (isBothLocalAndRemote || isRemoteOnly) {
-      buttons.add(
-        ButtonWidget(
-          labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.first,
-          shouldSurfaceExecutionStates: true,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesFromRemoteOnly(context, [file]);
-            showShortToast(context, "Moved to trash");
-            if (isRemoteOnly) {
-              Navigator.of(context, rootNavigator: true).pop();
-              widget.onFileRemoved(file);
-            }
-          },
-        ),
-      );
-    }
-    // Add option to delete from local
-    if (isBothLocalAndRemote || isLocalOnly) {
-      buttons.add(
-        ButtonWidget(
-          labelText:
-              isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.second,
-          shouldSurfaceExecutionStates: false,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesOnDeviceOnly(context, [file]);
-            if (isLocalOnly) {
-              Navigator.of(context, rootNavigator: true).pop();
-              widget.onFileRemoved(file);
-            }
-          },
-        ),
-      );
-    }
-
-    if (isBothLocalAndRemote) {
-      buttons.add(
-        ButtonWidget(
-          labelText: "Delete from both",
-          buttonType: ButtonType.neutral,
-          buttonSize: ButtonSize.large,
-          shouldStickToDarkTheme: true,
-          buttonAction: ButtonAction.third,
-          shouldSurfaceExecutionStates: true,
-          isInAlert: true,
-          onTap: () async {
-            await deleteFilesFromEverywhere(context, [file]);
-            Navigator.of(context, rootNavigator: true).pop();
-            widget.onFileRemoved(file);
-          },
-        ),
-      );
-    }
-    buttons.add(
-      const ButtonWidget(
-        labelText: "Cancel",
-        buttonType: ButtonType.secondary,
-        buttonSize: ButtonSize.large,
-        shouldStickToDarkTheme: true,
-        buttonAction: ButtonAction.fourth,
-        isInAlert: true,
-      ),
-    );
-    final actionResult = await showActionSheet(
-      context: context,
-      buttons: buttons,
-      actionSheetType: ActionSheetType.defaultActionSheet,
-      body: body,
-      bodyHighlight: bodyHighlight,
+  Future<void> _toggleFileArchiveStatus(File file) async {
+    final bool isArchived =
+        widget.file.magicMetadata.visibility == visibilityArchive;
+    await changeVisibility(
+      context,
+      [widget.file],
+      isArchived ? visibilityVisible : visibilityArchive,
     );
-    if (actionResult?.action != null &&
-        actionResult!.action == ButtonAction.error) {
-      showGenericErrorDialog(context: context);
+    if (mounted) {
+      setState(() {});
     }
   }
 

+ 23 - 41
lib/ui/viewer/file/fading_bottom_bar.dart

@@ -2,31 +2,30 @@ import 'dart:io';
 
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
-import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
-import 'package:photos/core/configuration.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file_type.dart';
-import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/models/trash_file.dart';
-import 'package:photos/services/collections_service.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/ente_theme.dart';
+import "package:photos/ui/actions/file/file_actions.dart";
 import 'package:photos/ui/collection_action_sheet.dart';
-import 'package:photos/ui/viewer/file/file_info_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
-import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/share_util.dart';
 
 class FadingBottomBar extends StatefulWidget {
   final File file;
   final Function(File) onEditRequested;
+  final Function(File) onFileRemoved;
   final bool showOnlyInfoButton;
+  final int? userID;
 
   const FadingBottomBar(
     this.file,
     this.onEditRequested,
     this.showOnlyInfoButton, {
+    required this.onFileRemoved,
+    this.userID,
     Key? key,
   }) : super(key: key);
 
@@ -63,6 +62,8 @@ class FadingBottomBarState extends State<FadingBottomBar> {
 
   Widget _getBottomBar() {
     final List<Widget> children = [];
+    final bool isOwnedByUser =
+        widget.file.ownerID == null || widget.file.ownerID == widget.userID;
     children.add(
       Tooltip(
         message: "Info",
@@ -88,15 +89,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
     if (widget.file is TrashFile) {
       _addTrashOptions(children);
     }
-    final bool isUploadedByUser = widget.file.uploadedFileID != null &&
-        widget.file.ownerID == Configuration.instance.getUserID();
-    bool isFileHidden = false;
-    if (isUploadedByUser) {
-      isFileHidden = CollectionsService.instance
-              .getCollectionByID(widget.file.collectionID!)
-              ?.isHidden() ??
-          false;
-    }
+
     if (!widget.showOnlyInfoButton && widget.file is! TrashFile) {
       if (widget.file.fileType == FileType.image ||
           widget.file.fileType == FileType.livePhoto) {
@@ -118,26 +111,21 @@ class FadingBottomBarState extends State<FadingBottomBar> {
           ),
         );
       }
-      if (isUploadedByUser && !isFileHidden) {
-        final bool isArchived =
-            widget.file.magicMetadata.visibility == visibilityArchive;
+      if (isOwnedByUser) {
         children.add(
           Tooltip(
-            message: isArchived ? "Unarchive" : "Archive",
+            message: "Delete",
             child: Padding(
               padding: const EdgeInsets.only(top: 12, bottom: 12),
               child: IconButton(
                 icon: Icon(
-                  isArchived ? Icons.unarchive : Icons.archive_outlined,
+                  Platform.isAndroid
+                      ? Icons.delete_outline
+                      : CupertinoIcons.delete,
                   color: Colors.white,
                 ),
                 onPressed: () async {
-                  await changeVisibility(
-                    context,
-                    [widget.file],
-                    isArchived ? visibilityVisible : visibilityArchive,
-                  );
-                  safeRefresh();
+                  await _showSingleFileDeleteSheet(widget.file);
                 },
               ),
             ),
@@ -223,6 +211,14 @@ class FadingBottomBarState extends State<FadingBottomBar> {
     );
   }
 
+  Future<void> _showSingleFileDeleteSheet(File file) async {
+    await showSingleFileDeleteSheet(
+      context,
+      file,
+      onFileRemoved: widget.onFileRemoved,
+    );
+  }
+
   void _addTrashOptions(List<Widget> children) {
     children.add(
       Tooltip(
@@ -272,20 +268,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
   }
 
   Future<void> _displayInfo(File file) async {
-    final colorScheme = getEnteColorScheme(context);
-    return showBarModalBottomSheet(
-      topControl: const SizedBox.shrink(),
-      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
-      backgroundColor: colorScheme.backgroundElevated,
-      barrierColor: backdropFaintDark,
-      context: context,
-      builder: (BuildContext context) {
-        return Padding(
-          padding:
-              EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
-          child: FileInfoWidget(file),
-        );
-      },
-    );
+    await showInfoSheet(context, file);
   }
 }

+ 3 - 42
lib/ui/viewer/gallery/archive_page.dart

@@ -1,17 +1,14 @@
 import 'package:collection/collection.dart' show IterableExtension;
 import 'package:flutter/material.dart';
-import "package:logging/logging.dart";
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/events/files_updated_event.dart';
-import "package:photos/models/collection_items.dart";
 import 'package:photos/models/gallery_type.dart';
 import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/services/collections_service.dart';
-import "package:photos/ui/collections/collection_item_widget.dart";
-import "package:photos/ui/common/loading_widget.dart";
+import "package:photos/ui/components/album_horizontal_list_widget.dart";
 import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart';
 import "package:photos/ui/viewer/gallery/empty_state.dart";
 import 'package:photos/ui/viewer/gallery/gallery.dart';
@@ -22,7 +19,6 @@ class ArchivePage extends StatelessWidget {
   final GalleryType appBarType;
   final GalleryType overlayType;
   final _selectedFiles = SelectedFiles();
-  final Logger _logger = Logger("ArchivePage");
 
   ArchivePage({
     this.tagPrefix = "archived_page",
@@ -70,43 +66,8 @@ class ArchivePage extends StatelessWidget {
       emptyState: const EmptyState(
         text: "You don't have any archived items.",
       ),
-      header: FutureBuilder(
-        future: CollectionsService.instance.getArchivedCollectionWithThumb(),
-        builder: (context, snapshot) {
-          if (snapshot.hasError) {
-            _logger.severe("failed to fetch archived albums", snapshot.error);
-            return const Text("Something went wrong");
-          } else if (snapshot.hasData) {
-            final collectionsWithThumbnail =
-                snapshot.data as List<CollectionWithThumbnail>;
-            return SizedBox(
-              height: 200,
-              child: ListView.builder(
-                shrinkWrap: true,
-                scrollDirection: Axis.horizontal,
-                itemCount: collectionsWithThumbnail.length,
-                padding: const EdgeInsets.fromLTRB(6, 6, 6, 6),
-                itemBuilder: (context, index) {
-                  final item = collectionsWithThumbnail[index];
-                  return GestureDetector(
-                    behavior: HitTestBehavior.opaque,
-                    onTap: () async {},
-                    child: Padding(
-                      padding: const EdgeInsets.all(2.0),
-                      child: CollectionItem(
-                        item,
-                        120,
-                        shouldRender: true,
-                      ),
-                    ),
-                  );
-                },
-              ),
-            );
-          } else {
-            return const EnteLoadingWidget();
-          }
-        },
+      header: AlbumHorizontalListWidget(
+        CollectionsService.instance.getArchivedCollectionWithThumb,
       ),
     );
     return Scaffold(

+ 13 - 37
lib/ui/viewer/gallery/collection_page.dart

@@ -16,13 +16,13 @@ import 'package:photos/ui/viewer/gallery/empty_state.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
 
-class CollectionPage extends StatefulWidget {
+class CollectionPage extends StatelessWidget {
   final CollectionWithThumbnail c;
   final String tagPrefix;
   final GalleryType appBarType;
   final bool hasVerifiedLock;
 
-  const CollectionPage(
+  CollectionPage(
     this.c, {
     this.tagPrefix = "collection",
     this.appBarType = GalleryType.ownedCollection,
@@ -30,42 +30,24 @@ class CollectionPage extends StatefulWidget {
     Key? key,
   }) : super(key: key);
 
-  @override
-  State<CollectionPage> createState() => _CollectionPageState();
-}
-
-class _CollectionPageState extends State<CollectionPage> {
   final _selectedFiles = SelectedFiles();
 
   final GlobalKey shareButtonKey = GlobalKey();
-  final ValueNotifier<double> _bottomPosition = ValueNotifier(-150.0);
-
-  @override
-  void initState() {
-    _selectedFiles.addListener(_selectedFilesListener);
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    _selectedFiles.removeListener(_selectedFilesListener);
-    super.dispose();
-  }
 
   @override
   Widget build(Object context) {
-    if (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) {
+    if (hasVerifiedLock == false && c.collection.isHidden()) {
       return const EmptyState();
     }
 
-    final appBarTypeValue = _getGalleryType(widget.c.collection);
+    final appBarTypeValue = _getGalleryType(c.collection);
     final List<File>? initialFiles =
-        widget.c.thumbnail != null ? [widget.c.thumbnail!] : null;
+        c.thumbnail != null ? [c.thumbnail!] : null;
     final gallery = Gallery(
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
         final FileLoadResult result =
             await FilesDB.instance.getFilesInCollection(
-          widget.c.collection.id,
+          c.collection.id,
           creationStartTime,
           creationEndTime,
           limit: limit,
@@ -82,25 +64,25 @@ class _CollectionPageState extends State<CollectionPage> {
       },
       reloadEvent: Bus.instance
           .on<CollectionUpdatedEvent>()
-          .where((event) => event.collectionID == widget.c.collection.id),
+          .where((event) => event.collectionID == c.collection.id),
       removalEventTypes: const {
         EventType.deletedFromRemote,
         EventType.deletedFromEverywhere,
         EventType.hide,
       },
-      tagPrefix: widget.tagPrefix,
+      tagPrefix: tagPrefix,
       selectedFiles: _selectedFiles,
       initialFiles: initialFiles,
-      albumName: widget.c.collection.name,
+      albumName: c.collection.name,
     );
     return Scaffold(
       appBar: PreferredSize(
         preferredSize: const Size.fromHeight(50.0),
         child: GalleryAppBarWidget(
           appBarTypeValue,
-          widget.c.collection.name,
+          c.collection.name,
           _selectedFiles,
-          collection: widget.c.collection,
+          collection: c.collection,
         ),
       ),
       body: Stack(
@@ -110,7 +92,7 @@ class _CollectionPageState extends State<CollectionPage> {
           FileSelectionOverlayBar(
             appBarTypeValue,
             _selectedFiles,
-            collection: widget.c.collection,
+            collection: c.collection,
           )
         ],
       ),
@@ -129,12 +111,6 @@ class _CollectionPageState extends State<CollectionPage> {
     } else if (c.type == CollectionType.favorites) {
       return GalleryType.favorite;
     }
-    return widget.appBarType;
-  }
-
-  _selectedFilesListener() {
-    _selectedFiles.files.isNotEmpty
-        ? _bottomPosition.value = 0.0
-        : _bottomPosition.value = -150.0;
+    return appBarType;
   }
 }

+ 7 - 28
lib/ui/viewer/gallery/trash_page.dart

@@ -14,7 +14,7 @@ import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 
-class TrashPage extends StatefulWidget {
+class TrashPage extends StatelessWidget {
   final String tagPrefix;
   final GalleryType appBarType;
   final GalleryType overlayType;
@@ -26,30 +26,9 @@ class TrashPage extends StatefulWidget {
     Key? key,
   }) : super(key: key);
 
-  @override
-  State<TrashPage> createState() => _TrashPageState();
-}
-
-class _TrashPageState extends State<TrashPage> {
-  late Function() _selectedFilesListener;
-  @override
-  void initState() {
-    _selectedFilesListener = () {
-      setState(() {});
-    };
-    widget._selectedFiles.addListener(_selectedFilesListener);
-    super.initState();
-  }
-
-  @override
-  void dispose() {
-    widget._selectedFiles.removeListener(_selectedFilesListener);
-    super.dispose();
-  }
-
   @override
   Widget build(Object context) {
-    final bool filesAreSelected = widget._selectedFiles.files.isNotEmpty;
+    final bool filesAreSelected = _selectedFiles.files.isNotEmpty;
 
     final gallery = Gallery(
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
@@ -70,8 +49,8 @@ class _TrashPageState extends State<TrashPage> {
       forceReloadEvents: [
         Bus.instance.on<ForceReloadTrashPageEvent>(),
       ],
-      tagPrefix: widget.tagPrefix,
-      selectedFiles: widget._selectedFiles,
+      tagPrefix: tagPrefix,
+      selectedFiles: _selectedFiles,
       header: _headerWidget(),
       initialFiles: null,
     );
@@ -80,9 +59,9 @@ class _TrashPageState extends State<TrashPage> {
       appBar: PreferredSize(
         preferredSize: const Size.fromHeight(50.0),
         child: GalleryAppBarWidget(
-          widget.appBarType,
+          appBarType,
           "Trash",
-          widget._selectedFiles,
+          _selectedFiles,
         ),
       ),
       body: Stack(
@@ -109,7 +88,7 @@ class _TrashPageState extends State<TrashPage> {
               ),
             ),
           ),
-          FileSelectionOverlayBar(GalleryType.trash, widget._selectedFiles)
+          FileSelectionOverlayBar(GalleryType.trash, _selectedFiles)
         ],
       ),
     );

+ 4 - 0
lib/utils/delete_file_util.dart

@@ -13,6 +13,7 @@ import 'package:photos/core/event_bus.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/events/collection_updated_event.dart';
 import 'package:photos/events/files_updated_event.dart';
+import "package:photos/events/force_reload_trash_page_event.dart";
 import 'package:photos/events/local_photos_updated_event.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/selected_files.dart';
@@ -264,6 +265,9 @@ Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
             source: "deleteFromTrash",
           ),
         );
+        //the FilesUpdateEvent is not reloading trash on premanently removing
+        //files, so need to fire ForceReloadTrashPageEvent
+        Bus.instance.fire(ForceReloadTrashPageEvent());
       } catch (e, s) {
         _logger.info("failed to delete from trash", e, s);
         rethrow;

+ 3 - 2
lib/utils/dialog_util.dart

@@ -43,14 +43,15 @@ Future<ButtonResult?> showErrorDialogForException({
   required BuildContext context,
   required Exception exception,
   bool isDismissible = true,
+  String apiErrorPrefix = "It looks like something went wrong.",
 }) async {
   String errorMessage =
       "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.";
   if (exception is DioError &&
       exception.response != null &&
       exception.response!.data["code"] != null) {
-    errorMessage = "It looks like something went wrong. \n\nReason: " +
-        exception.response!.data["code"];
+    errorMessage =
+        "$apiErrorPrefix\n\nReason: " + exception.response!.data["code"];
   }
   return showDialogWidget(
     context: context,

+ 6 - 4
lib/utils/file_uploader.dart

@@ -6,7 +6,7 @@ import 'dart:math';
 import 'dart:typed_data';
 
 import 'package:collection/collection.dart';
-import 'package:connectivity/connectivity.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:dio/dio.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
@@ -282,9 +282,11 @@ class FileUploader {
       return;
     }
     final connectivityResult = await (Connectivity().checkConnectivity());
-    final canUploadUnderCurrentNetworkConditions =
-        (connectivityResult == ConnectivityResult.wifi ||
-            Configuration.instance.shouldBackupOverMobileData());
+    bool canUploadUnderCurrentNetworkConditions = true;
+    if (connectivityResult == ConnectivityResult.mobile) {
+      canUploadUnderCurrentNetworkConditions =
+          Configuration.instance.shouldBackupOverMobileData();
+    }
     if (!canUploadUnderCurrentNetworkConditions) {
       throw WiFiUnavailableError();
     }

+ 16 - 0
lib/utils/intent_util.dart

@@ -0,0 +1,16 @@
+import "package:flutter/services.dart";
+import "package:media_extension/media_extension.dart";
+import "package:media_extension/media_extension_action_types.dart";
+
+Future<MediaExtentionAction> initIntentAction() async {
+  final mediaExtensionPlugin = MediaExtension();
+  MediaExtentionAction mediaExtensionAction;
+  try {
+    mediaExtensionAction = await mediaExtensionPlugin.getIntentAction();
+  } on PlatformException {
+    mediaExtensionAction = MediaExtentionAction(action: IntentAction.main);
+  } catch (error) {
+    mediaExtensionAction = MediaExtentionAction(action: IntentAction.main);
+  }
+  return mediaExtensionAction;
+}

+ 24 - 24
pubspec.lock

@@ -208,38 +208,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "0.6.0"
-  connectivity:
+  connectivity_plus:
     dependency: "direct main"
     description:
-      name: connectivity
-      sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8
+      name: connectivity_plus
+      sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.6"
-  connectivity_for_web:
-    dependency: transitive
-    description:
-      name: connectivity_for_web
-      sha256: "01a390c1d5adc2ed1fa1f52d120c07fe9fd01166a93f965a832fd6cfc0ea6482"
-      url: "https://pub.dev"
-    source: hosted
-    version: "0.4.0+1"
-  connectivity_macos:
-    dependency: transitive
-    description:
-      name: connectivity_macos
-      sha256: "51ae08d5162eca9669b9d8951ed83ce19c5355a81149f94e4dee2740beb93628"
-      url: "https://pub.dev"
-    source: hosted
-    version: "0.2.1+2"
-  connectivity_platform_interface:
+    version: "3.0.3"
+  connectivity_plus_platform_interface:
     dependency: transitive
     description:
-      name: connectivity_platform_interface
-      sha256: "2d82e942df9d49f29a24bb07fb5ce085d4a53e47818c62364d2b6deb9e0d7a8e"
+      name: connectivity_plus_platform_interface
+      sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.1"
+    version: "1.2.4"
   convert:
     dependency: transitive
     description:
@@ -493,6 +477,14 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_animate:
+    dependency: "direct main"
+    description:
+      name: flutter_animate
+      sha256: c6e38af9fe376fa07fb6995fe6a12d07a08828d2847aa652b7c1bc60f9993374
+      url: "https://pub.dev"
+    source: hosted
+    version: "4.1.0"
   flutter_blurhash:
     dependency: transitive
     description:
@@ -1005,6 +997,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.0.0"
+  nm:
+    dependency: transitive
+    description:
+      name: nm
+      sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.0"
   node_preamble:
     dependency: transitive
     description:

+ 12 - 3
pubspec.yaml

@@ -12,7 +12,7 @@ description: ente photos application
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 
-version: 0.7.26+426
+version: 0.7.27+427
 
 environment:
   sdk: '>=2.17.0 <3.0.0'
@@ -30,7 +30,7 @@ dependencies:
   collection: # dart
   computer: ^2.0.0
   confetti: ^0.6.0
-  connectivity: ^3.0.3
+  connectivity_plus: ^3.0.3
   cupertino_icons: ^1.0.0
   device_info: ^2.0.2
   dio: ^4.0.6
@@ -49,6 +49,7 @@ dependencies:
   fk_user_agent: ^2.0.1
   flutter:
     sdk: flutter
+  flutter_animate: ^4.1.0
   flutter_cache_manager: ^3.3.0
   flutter_datetime_picker: ^1.5.1
   flutter_easyloading: ^3.0.0
@@ -76,7 +77,7 @@ dependencies:
   local_auth: ^1.1.5
   logging: ^1.0.1
   lottie: ^1.2.2
-  media_extension: ^1.0.0
+  media_extension: ^1.0.1
   modal_bottom_sheet: ^3.0.0-pre
   motionphoto:
     git: "https://github.com/ente-io/motionphoto.git"
@@ -147,6 +148,14 @@ flutter_native_splash:
   android_fullscreen: true
   android_gravity: center
   ios_content_mode: center
+  android_12:
+    # The image parameter sets the splash screen icon image.  If this parameter is not specified,
+    # the app's launcher icon will be used instead.
+    # Please note that the splash screen will be clipped to a circle on the center of the screen.
+    # App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
+    # 768 pixels in diameter.
+    image: assets/splash-screen-light.png
+    image_dark: assets/splash-screen-dark.png
 
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec

+ 7 - 9
run.sh

@@ -2,14 +2,12 @@
 
 FLUTTER_RUN="flutter run --flavor dev "
 
-if [ ! -z "$1" ]
-then
-    SUPPLIED_ENV_FILE="$1"
-    while IFS= read -r line
-    do
-      FLUTTER_RUN="$FLUTTER_RUN --dart-define $line"
-
-    done < "$SUPPLIED_ENV_FILE"
-fi
+SUPPLIED_ENV_FILE=".env"
+while IFS= read -r line
+do
+    FLUTTER_RUN="$FLUTTER_RUN --dart-define $line"
+
+done < "$SUPPLIED_ENV_FILE"
+
 echo "Running: $FLUTTER_RUN"
 $FLUTTER_RUN