Ver Fonte

Resolved merge conflicts

ashilkn há 2 anos atrás
pai
commit
afa0309f39
84 ficheiros alterados com 1122 adições e 595 exclusões
  1. 71 70
      android/app/src/main/AndroidManifest.xml
  2. BIN
      android/app/src/main/res/drawable-hdpi/android12splash.png
  3. BIN
      android/app/src/main/res/drawable-hdpi/splash.png
  4. BIN
      android/app/src/main/res/drawable-mdpi/android12splash.png
  5. BIN
      android/app/src/main/res/drawable-mdpi/splash.png
  6. BIN
      android/app/src/main/res/drawable-night-hdpi/android12splash.png
  7. BIN
      android/app/src/main/res/drawable-night-hdpi/splash.png
  8. BIN
      android/app/src/main/res/drawable-night-mdpi/android12splash.png
  9. BIN
      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
      android/app/src/main/res/drawable-night-xhdpi/android12splash.png
  12. BIN
      android/app/src/main/res/drawable-night-xhdpi/splash.png
  13. BIN
      android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
  14. BIN
      android/app/src/main/res/drawable-night-xxhdpi/splash.png
  15. BIN
      android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
  16. BIN
      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
      android/app/src/main/res/drawable-xhdpi/android12splash.png
  20. BIN
      android/app/src/main/res/drawable-xhdpi/splash.png
  21. BIN
      android/app/src/main/res/drawable-xxhdpi/android12splash.png
  22. BIN
      android/app/src/main/res/drawable-xxhdpi/splash.png
  23. BIN
      android/app/src/main/res/drawable-xxxhdpi/android12splash.png
  24. BIN
      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
      assets/splash-screen-dark.png
  31. BIN
      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
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
  36. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
  37. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
  38. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png
  39. BIN
      ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png
  40. BIN
      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"
 <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}"
     <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"
         <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">
             <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>
 
 
             <intent-filter android:label="@string/backup">
             <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>
 
 
             <intent-filter android:label="@string/backup">
             <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>
 
 
             <intent-filter android:label="@string/backup">
             <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>
             </intent-filter>
 
 
         </activity>
         </activity>
 
 
         <!-- Don't delete the meta-data below.
         <!-- Don't delete the meta-data below.
              This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
              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"
         <meta-data android:name="asset_statements"
-                   android:resource="@string/asset_statements"/>
+            android:resource="@string/asset_statements" />
         <meta-data android:name="io.sentry.dsn"
         <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"
         <meta-data android:name="firebase_analytics_collection_deactivated"
-                   android:value="true"/>
+            android:value="true" />
     </application>
     </application>
 
 
     <!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
     <!-- Android 11: https://developer.android.com/preview/privacy/package-visibility -->
     <!-- https://developer.android.com/training/package-visibility/use-cases -->
     <!-- https://developer.android.com/training/package-visibility/use-cases -->
     <queries>
     <queries>
         <intent>
         <intent>
-            <action android:name="android.intent.action.SENDTO"/>
-            <data android:scheme="mailto"/>
+            <action android:name="android.intent.action.SENDTO" />
+            <data android:scheme="mailto" />
         </intent>
         </intent>
     </queries>
     </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
     <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
     <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
     <uses-permission
-            android:name="android.permission.READ_EXTERNAL_STORAGE"
-            android:maxSdkVersion="32"/>
+        android:name="android.permission.READ_EXTERNAL_STORAGE"
+        android:maxSdkVersion="32" />
     <uses-permission
     <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>
 </manifest>

BIN
android/app/src/main/res/drawable-hdpi/android12splash.png


BIN
android/app/src/main/res/drawable-hdpi/splash.png


BIN
android/app/src/main/res/drawable-mdpi/android12splash.png


BIN
android/app/src/main/res/drawable-mdpi/splash.png


BIN
android/app/src/main/res/drawable-night-hdpi/android12splash.png


BIN
android/app/src/main/res/drawable-night-hdpi/splash.png


BIN
android/app/src/main/res/drawable-night-mdpi/android12splash.png


BIN
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>
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
     </item>
-</layer-list>
+</layer-list>

BIN
android/app/src/main/res/drawable-night-xhdpi/android12splash.png


BIN
android/app/src/main/res/drawable-night-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-night-xxhdpi/android12splash.png


BIN
android/app/src/main/res/drawable-night-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png


BIN
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>
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
     </item>
-</layer-list>
+</layer-list>

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

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

BIN
android/app/src/main/res/drawable-xhdpi/android12splash.png


BIN
android/app/src/main/res/drawable-xhdpi/splash.png


BIN
android/app/src/main/res/drawable-xxhdpi/android12splash.png


BIN
android/app/src/main/res/drawable-xxhdpi/splash.png


BIN
android/app/src/main/res/drawable-xxxhdpi/android12splash.png


BIN
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>
     <item>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
         <bitmap android:gravity="center" android:src="@drawable/splash"/>
     </item>
     </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">
 <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
   <background android:drawable="@color/ic_launcher_background"/>
   <background android:drawable="@color/ic_launcher_background"/>
   <foreground android:drawable="@drawable/ic_launcher_foreground"/>
   <foreground android:drawable="@drawable/ic_launcher_foreground"/>
+  <monochrome android:drawable="@drawable/ic_launcher_foreground"/>
 </adaptive-icon>
 </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">
     <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
         <item name="android:windowSplashScreenBackground">#000000</item>
         <item name="android:windowSplashScreenBackground">#000000</item>
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
     </style>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
          This theme determines the color of the Android Window while your
@@ -15,4 +17,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
     </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:windowBackground">@drawable/launch_background</item>
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
     </style>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
          This theme determines the color of the Android Window while your
@@ -17,4 +18,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
     <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
     </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">
     <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:forceDarkAllowed">false</item>
         <item name="android:windowFullscreen">false</item>
         <item name="android:windowFullscreen">false</item>
+        <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
         <item name="android:windowSplashScreenBackground">#ffffff</item>
         <item name="android:windowSplashScreenBackground">#ffffff</item>
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
     </style>
     </style>
     <!-- Theme applied to the Android Window as soon as the process has started.
     <!-- Theme applied to the Android Window as soon as the process has started.
          This theme determines the color of the Android Window while your
          This theme determines the color of the Android Window while your
@@ -15,4 +17,4 @@
     <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
     <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
         <item name="android:windowBackground">?android:colorBackground</item>
         <item name="android:windowBackground">?android:colorBackground</item>
     </style>
     </style>
-</resources>
+</resources>

BIN
assets/splash-screen-dark.png


BIN
assets/splash-screen-light.png


+ 9 - 9
ios/Podfile.lock

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

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

@@ -268,14 +268,14 @@
 				"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
 				"${BUILT_PRODUCTS_DIR}/Mantle/Mantle.framework",
 				"${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework",
 				"${BUILT_PRODUCTS_DIR}/OrderedSet/OrderedSet.framework",
 				"${BUILT_PRODUCTS_DIR}/PromisesObjC/FBLPromises.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}/SDWebImage/SDWebImage.framework",
 				"${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
 				"${BUILT_PRODUCTS_DIR}/SDWebImageWebPCoder/SDWebImageWebPCoder.framework",
 				"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
 				"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
 				"${BUILT_PRODUCTS_DIR}/Toast/Toast.framework",
 				"${BUILT_PRODUCTS_DIR}/Toast/Toast.framework",
 				"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
 				"${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework",
 				"${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.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}/device_info/device_info.framework",
 				"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
 				"${BUILT_PRODUCTS_DIR}/fk_user_agent/fk_user_agent.framework",
 				"${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.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}/Toast.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.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}/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}/device_info.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/fk_user_agent.framework",
 				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.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" : [
   "images" : [
     {
     {
       "filename" : "background.png",
       "filename" : "background.png",
-      "idiom" : "universal",
-      "scale" : "1x"
+      "idiom" : "universal"
     },
     },
     {
     {
       "appearances" : [
       "appearances" : [
@@ -13,36 +12,7 @@
         }
         }
       ],
       ],
       "filename" : "darkbackground.png",
       "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" : {
   "info" : {

BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png


BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png


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

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

+ 14 - 15
ios/Runner/Info.plist

@@ -23,19 +23,19 @@
 		<key>CFBundleSignature</key>
 		<key>CFBundleSignature</key>
 		<string>????</string>
 		<string>????</string>
 		<key>MinimumOSVersion</key>
 		<key>MinimumOSVersion</key>
-        <string>12.0</string>
+		<string>12.0</string>
 		<key>LSApplicationQueriesSchemes</key>
 		<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>
 		<key>CFBundleURLTypes</key>
 		<array>
 		<array>
 			<dict>
 			<dict>
@@ -67,8 +67,7 @@
 		<key>NSFaceIDUsageDescription</key>
 		<key>NSFaceIDUsageDescription</key>
 		<string>Please allow ente to lock itself with FaceID or TouchID</string>
 		<string>Please allow ente to lock itself with FaceID or TouchID</string>
 		<key>NSCameraUsageDescription</key>
 		<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>
 		<key>NSPhotoLibraryUsageDescription</key>
 		<string>Please allow access to your photos so that ente can encrypt and back them up.</string>
 		<string>Please allow access to your photos so that ente can encrypt and back them up.</string>
 		<key>UIBackgroundModes</key>
 		<key>UIBackgroundModes</key>
@@ -102,5 +101,5 @@
 		<true/>
 		<true/>
 		<key>UIApplicationSupportsIndirectInputEvents</key>
 		<key>UIApplicationSupportsIndirectInputEvents</key>
 		<true/>
 		<true/>
-</dict>
+	</dict>
 </plist>
 </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:background_fetch/background_fetch.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_easyloading/flutter_easyloading.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
-import 'package:media_extension/media_extension.dart';
 import 'package:media_extension/media_extension_action_types.dart';
 import 'package:media_extension/media_extension_action_types.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/app_lifecycle_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/ui/home_widget.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 {
 class EnteApp extends StatefulWidget {
   final Future<void> Function(String) runBackgroundTask;
   final Future<void> Function(String) runBackgroundTask;
@@ -31,63 +31,46 @@ class EnteApp extends StatefulWidget {
 
 
 class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
 class _EnteAppState extends State<EnteApp> with WidgetsBindingObserver {
   final _logger = Logger("EnteAppState");
   final _logger = Logger("EnteAppState");
-  final _mediaExtensionPlugin = MediaExtension();
 
 
   @override
   @override
   void initState() {
   void initState() {
     _logger.info('init App');
     _logger.info('init App');
     super.initState();
     super.initState();
+    setupIntentAction();
     WidgetsBinding.instance.addObserver(this);
     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();
       _configureBackgroundFetch();
     }
     }
-    AppLifecycleService.instance.setIntentAction(intentAction);
-    return true;
   }
   }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     if (Platform.isAndroid || kDebugMode) {
     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 {
     } else {
       return MaterialApp(
       return MaterialApp(

+ 6 - 2
lib/db/files_db.dart

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

+ 3 - 1
lib/extensions/input_formatter.dart

@@ -3,7 +3,9 @@ import "package:flutter/services.dart";
 class UpperCaseTextFormatter extends TextInputFormatter {
 class UpperCaseTextFormatter extends TextInputFormatter {
   @override
   @override
   TextEditingValue formatEditUpdate(
   TextEditingValue formatEditUpdate(
-      TextEditingValue oldValue, TextEditingValue newValue) {
+    TextEditingValue oldValue,
+    TextEditingValue newValue,
+  ) {
     return TextEditingValue(
     return TextEditingValue(
       text: newValue.text.toUpperCase(),
       text: newValue.text.toUpperCase(),
       selection: newValue.selection,
       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:firebase_messaging/firebase_messaging.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import "package:flutter/rendering.dart";
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:photos/app.dart';
 import 'package:photos/app.dart';
@@ -54,6 +55,7 @@ const kFGTaskDeathTimeoutInMicroseconds = 5000000;
 const kBackgroundLockLatency = Duration(seconds: 3);
 const kBackgroundLockLatency = Duration(seconds: 3);
 
 
 void main() async {
 void main() async {
+  debugRepaintRainbowEnabled = false;
   WidgetsFlutterBinding.ensureInitialized();
   WidgetsFlutterBinding.ensureInitialized();
   await _runInForeground();
   await _runInForeground();
   BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);
   BackgroundFetch.registerHeadlessTask(_headlessTaskHandler);

+ 5 - 3
lib/services/app_lifecycle_service.dart

@@ -5,15 +5,17 @@ class AppLifecycleService {
   final _logger = Logger("AppLifecycleService");
   final _logger = Logger("AppLifecycleService");
 
 
   bool isForeground = false;
   bool isForeground = false;
-  IntentAction intentAction = IntentAction.main;
+  MediaExtentionAction mediaExtensionAction =
+      MediaExtentionAction(action: IntentAction.main);
 
 
   static final AppLifecycleService instance =
   static final AppLifecycleService instance =
       AppLifecycleService._privateConstructor();
       AppLifecycleService._privateConstructor();
 
 
   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) {
   void onAppInForeground(String reason) {

+ 3 - 1
lib/services/collections_service.dart

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

+ 13 - 0
lib/services/memories_service.dart

@@ -1,8 +1,10 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/constants.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/files_db.dart';
 import 'package:photos/db/memories_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/filters/important_items_filter.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/collections_service.dart';
@@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
         DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay),
         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() {
   void clearCache() {

+ 7 - 3
lib/services/storage_bonus_service.dart

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

+ 1 - 1
lib/services/sync_service.dart

@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
-import 'package:connectivity/connectivity.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photo_manager/photo_manager.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 warning800;
   final Color caution500;
   final Color caution500;
 
 
+  //golden colors
+  final Color golden700;
+  final Color golden500;
+
   //other colors
   //other colors
   final Color tabIcon;
   final Color tabIcon;
   final List<Color> avatarColors;
   final List<Color> avatarColors;
@@ -86,6 +90,8 @@ class EnteColorScheme {
     this.warning500 = _warning500,
     this.warning500 = _warning500,
     this.warning400 = _warning400,
     this.warning400 = _warning400,
     this.caution500 = _caution500,
     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 _caution500 = Color.fromRGBO(255, 194, 71, 1);
 
 
+const Color _golden700 = Color(0xFFFDB816);
+const Color _golden500 = Color(0xFFFFC336);
+
 const List<Color> avatarLight = [
 const List<Color> avatarLight = [
   Color.fromRGBO(118, 84, 154, 1),
   Color.fromRGBO(118, 84, 154, 1),
   Color.fromRGBO(223, 120, 97, 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 brandSmall;
   final TextStyle brandMedium;
   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({
   const EnteTextTheme({
     required this.h1,
     required this.h1,
     required this.h1Bold,
     required this.h1Bold,
@@ -105,13 +125,42 @@ class EnteTextTheme {
     required this.tinyBold,
     required this.tinyBold,
     required this.brandSmall,
     required this.brandSmall,
     required this.brandMedium,
     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(
   return EnteTextTheme(
     h1: h1.copyWith(color: color),
     h1: h1.copyWith(color: color),
     h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight),
     h1Bold: h1.copyWith(color: color, fontWeight: _boldWeight),
@@ -131,5 +180,21 @@ EnteTextTheme _buildEnteTextStyle(Color color) {
     tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight),
     tinyBold: tiny.copyWith(color: color, fontWeight: _boldWeight),
     brandSmall: brandStyleSmall.copyWith(color: color),
     brandSmall: brandStyleSmall.copyWith(color: color),
     brandMedium: brandStyleMedium.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 {
   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();
     await dialog.show();
     try {
     try {
       await CollectionsService.instance
       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/core/configuration.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/models/magic_metadata.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/ui/viewer/gallery/archive_page.dart';
 import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/navigation_util.dart';
 
 
@@ -15,6 +16,8 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
+    final Set<int> hiddenCollectionId =
+        CollectionsService.instance.getHiddenCollections();
     return OutlinedButton(
     return OutlinedButton(
       style: OutlinedButton.styleFrom(
       style: OutlinedButton.styleFrom(
         backgroundColor: Theme.of(context).backgroundColor,
         backgroundColor: Theme.of(context).backgroundColor,
@@ -43,9 +46,10 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
                   ),
                   ),
                   const Padding(padding: EdgeInsets.all(6)),
                   const Padding(padding: EdgeInsets.all(6)),
                   FutureBuilder<int>(
                   FutureBuilder<int>(
-                    future: FilesDB.instance.fileCountWithVisibility(
+                    future: FilesDB.instance.archivedFilesCount(
                       visibilityArchive,
                       visibilityArchive,
                       Configuration.instance.getUserID()!,
                       Configuration.instance.getUserID()!,
+                      hiddenCollectionId,
                     ),
                     ),
                     builder: (context, snapshot) {
                     builder: (context, snapshot) {
                       if (snapshot.hasData && snapshot.data! > 0) {
                       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:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/theme/colors.dart';
 import 'package:photos/theme/colors.dart';
+import "package:photos/theme/ente_theme.dart";
 import 'package:photos/theme/text_style.dart';
 import 'package:photos/theme/text_style.dart';
 import 'package:photos/ui/components/icon_button_widget.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 {
 enum NotificationType {
   warning,
   warning,
   banner,
   banner,
+  goldenBanner,
 }
 }
 
 
 class NotificationWidget extends StatelessWidget {
 class NotificationWidget extends StatelessWidget {
@@ -30,7 +32,9 @@ class NotificationWidget extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    Color backgroundColor = Colors.white;
+    final colorScheme = getEnteColorScheme(context);
+    LinearGradient? backgroundGradient;
+    Color? backgroundColor;
     switch (type) {
     switch (type) {
       case NotificationType.warning:
       case NotificationType.warning:
         backgroundColor = warning500;
         backgroundColor = warning500;
@@ -38,6 +42,13 @@ class NotificationWidget extends StatelessWidget {
       case NotificationType.banner:
       case NotificationType.banner:
         backgroundColor = backgroundElevated2Dark;
         backgroundColor = backgroundElevated2Dark;
         break;
         break;
+      case NotificationType.goldenBanner:
+        backgroundGradient = LinearGradient(
+          colors: [colorScheme.golden700, colorScheme.golden500],
+          stops: const [0.25, 1],
+          begin: Alignment.bottomCenter,
+          end: Alignment.topCenter,
+        );
     }
     }
     return Center(
     return Center(
       child: GestureDetector(
       child: GestureDetector(
@@ -49,6 +60,7 @@ class NotificationWidget extends StatelessWidget {
             ),
             ),
             boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu,
             boxShadow: Theme.of(context).colorScheme.enteTheme.shadowMenu,
             color: backgroundColor,
             color: backgroundColor,
+            gradient: backgroundGradient,
           ),
           ),
           child: Padding(
           child: Padding(
             padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
             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:flutter/material.dart";
 import "package:logging/logging.dart";
 import "package:logging/logging.dart";
 import "package:photos/extensions/input_formatter.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/services/storage_bonus_service.dart";
 import "package:photos/theme/ente_theme.dart";
 import "package:photos/theme/ente_theme.dart";
 import "package:photos/ui/components/button_widget.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/models/button_type.dart";
 import "package:photos/ui/components/title_bar_title_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/components/title_bar_widget.dart";
+import "package:photos/ui/growth/code_success_screen.dart";
 import "package:photos/utils/dialog_util.dart";
 import "package:photos/utils/dialog_util.dart";
 
 
 class ApplyCodeScreen extends StatefulWidget {
 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
   @override
   State<ApplyCodeScreen> createState() => _ApplyCodeScreenState();
   State<ApplyCodeScreen> createState() => _ApplyCodeScreenState();
@@ -112,14 +122,22 @@ class _ApplyCodeScreenState extends State<ApplyCodeScreen> {
                           await StorageBonusService.instance
                           await StorageBonusService.instance
                               .getGateway()
                               .getGateway()
                               .claimReferralCode(code.trim().toUpperCase());
                               .claimReferralCode(code.trim().toUpperCase());
-                          Navigator.of(context).pop();
+
+                          Navigator.of(context).pushReplacement(
+                            MaterialPageRoute(
+                              builder: (context) => CodeSuccessScreen(
+                                widget.referralView,
+                                widget.userDetails,
+                              ),
+                            ),
+                          );
                         } catch (e) {
                         } catch (e) {
                           Logger('$runtimeType')
                           Logger('$runtimeType')
                               .severe("failed to apply referral", e);
                               .severe("failed to apply referral", e);
                           showErrorDialogForException(
                           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:flutter/material.dart";
 import "package:photos/models/api/storage_bonus/storage_bonus.dart";
 import "package:photos/models/api/storage_bonus/storage_bonus.dart";
 import "package:photos/models/user_details.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_title_widget.dart";
 import "package:photos/ui/components/title_bar_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/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/ui/growth/storage_details_screen.dart";
 import "package:photos/utils/data_util.dart";
 import "package:photos/utils/data_util.dart";
 import "package:photos/utils/navigation_util.dart";
 import "package:photos/utils/navigation_util.dart";
@@ -129,7 +129,8 @@ class ReferralWidget extends StatelessWidget {
             ? InkWell(
             ? InkWell(
                 onTap: () {
                 onTap: () {
                   shareText(
                   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(
                 child: Container(
                   width: double.infinity,
                   width: double.infinity,
@@ -153,41 +154,7 @@ class ReferralWidget extends StatelessWidget {
                           "friends",
                           "friends",
                         ),
                         ),
                         const SizedBox(height: 12),
                         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 SizedBox(height: 12),
                         const Text(
                         const Text(
                           "2. They sign up for a paid plan",
                           "2. They sign up for a paid plan",
@@ -213,9 +180,11 @@ class ReferralWidget extends StatelessWidget {
                         color: colorScheme.strokeMuted,
                         color: colorScheme.strokeMuted,
                       ),
                       ),
                       const SizedBox(height: 12),
                       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 {
                 onTap: () async {
                   await routeToPage(
                   await routeToPage(
                     context,
                     context,
-                    const ApplyCodeScreen(),
+                    ApplyCodeScreen(referralView, userDetails),
                   );
                   );
                   notifyParent();
                   notifyParent();
                 },
                 },
@@ -273,9 +242,12 @@ class ReferralWidget extends StatelessWidget {
           alignCaptionedTextToLeft: true,
           alignCaptionedTextToLeft: true,
           onTap: () async {
           onTap: () async {
             routeToPage(
             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),
         const SizedBox(height: 24),

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

@@ -108,19 +108,23 @@ class _StorageDetailsScreenState extends State<StorageDetailsScreen> {
                                 BonusInfoSection(
                                 BonusInfoSection(
                                   sectionName: "Free storage claimed",
                                   sectionName: "Free storage claimed",
                                   leftValue: convertBytesToAbsoluteGBs(
                                   leftValue: convertBytesToAbsoluteGBs(
-                                      widget.referralView.claimedStorage),
+                                    widget.referralView.claimedStorage,
+                                  ),
                                   leftUnitName: "GB",
                                   leftUnitName: "GB",
                                   rightValue: null,
                                   rightValue: null,
                                 ),
                                 ),
                                 BonusInfoSection(
                                 BonusInfoSection(
                                   sectionName: "Free storage usable",
                                   sectionName: "Free storage usable",
-                                  leftValue: convertBytesToAbsoluteGBs(min(
-                                    widget.referralView.claimedStorage,
-                                    widget.userDetails.getTotalStorage(),
-                                  )),
+                                  leftValue: convertBytesToAbsoluteGBs(
+                                    min(
+                                      widget.referralView.claimedStorage,
+                                      widget.userDetails.getTotalStorage(),
+                                    ),
+                                  ),
                                   leftUnitName: "GB",
                                   leftUnitName: "GB",
                                   rightValue: convertBytesToAbsoluteGBs(
                                   rightValue: convertBytesToAbsoluteGBs(
-                                      widget.userDetails.getTotalStorage()),
+                                    widget.userDetails.getTotalStorage(),
+                                  ),
                                   rightUnitName: "GB",
                                   rightUnitName: "GB",
                                 ),
                                 ),
                                 const SizedBox(
                                 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:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/models/memory.dart';
 import 'package:photos/services/memories_service.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/file_widget.dart';
 import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
 import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
 import 'package:photos/utils/date_time_util.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() {
   Hero _buildInfoText() {
     return Hero(
     return Hero(
       tag: widget.title,
       tag: widget.title,
@@ -346,17 +365,48 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
 
 
   Widget _buildBottomIcons() {
   Widget _buildBottomIcons() {
     final file = widget.memories[_index].file;
     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() {
   Widget _buildSwiper() {
     _pageController = PageController(initialPage: _index);
     _pageController = PageController(initialPage: _index);
-    return ExtentsPageView.extents(
+    return PageView.builder(
       itemBuilder: (BuildContext context, int index) {
       itemBuilder: (BuildContext context, int index) {
         if (index < widget.memories.length - 1) {
         if (index < widget.memories.length - 1) {
           final nextFile = widget.memories[index + 1].file;
           final nextFile = widget.memories[index + 1].file;
@@ -405,7 +455,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
       },
       },
       itemCount: widget.memories.length,
       itemCount: widget.memories.length,
       controller: _pageController,
       controller: _pageController,
-      extents: 1,
       onPageChanged: (index) async {
       onPageChanged: (index) async {
         await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
         await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
         if (mounted) {
         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");
     _logger.info("Building home_Widget with tab $_selectedTabIndex");
     bool isSettingsOpen = false;
     bool isSettingsOpen = false;
     final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport();
     final enableDrawer = LocalSyncService.instance.hasCompletedFirstImport();
-
+    final action = AppLifecycleService.instance.mediaExtensionAction.action;
     return UserDetailsStateWidget(
     return UserDetailsStateWidget(
       child: WillPopScope(
       child: WillPopScope(
         child: Scaffold(
         child: Scaffold(
@@ -298,9 +298,7 @@ class _HomeWidgetState extends State<HomeWidget> {
               Navigator.pop(context);
               Navigator.pop(context);
               return false;
               return false;
             }
             }
-            if (Platform.isAndroid &&
-                AppLifecycleService.instance.intentAction ==
-                    IntentAction.main) {
+            if (Platform.isAndroid && action == IntentAction.main) {
               MoveToBackground.moveTaskToBack();
               MoveToBackground.moveTaskToBack();
               return false;
               return false;
             } else {
             } else {

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

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

+ 20 - 13
lib/ui/settings_page.dart

@@ -2,6 +2,7 @@ import 'dart:io';
 
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
+import "package:flutter_animate/flutter_animate.dart";
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/events/opened_settings_event.dart';
 import 'package:photos/events/opened_settings_event.dart';
@@ -77,19 +78,25 @@ class SettingsPage extends StatelessWidget {
       contents.addAll([
       contents.addAll([
         const StorageCardWidget(),
         const StorageCardWidget(),
         StorageBonusService.instance.shouldShowStorageBonus()
         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 SizedBox(height: 12),
         const BackupSectionWidget(),
         const BackupSectionWidget(),

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

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

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

@@ -100,8 +100,9 @@ class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
             "${collections.length}";
             "${collections.length}";
         try {
         try {
           await CollectionsService.instance.trashEmptyCollection(
           await CollectionsService.instance.trashEmptyCollection(
-              collections[i].collection,
-              isBulkDelete: true);
+            collections[i].collection,
+            isBulkDelete: true,
+          );
         } catch (_) {
         } catch (_) {
           failedCount++;
           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 CollectionActions collectionActions;
   late bool isCollectionOwner;
   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
   // links if user keeps on creating Create link button after selecting
   // few files. This link is reset on any selection changed;
   // few files. This link is reset on any selection changed;
   Collection? _cachedCollectionForSharedLink;
   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],
               _files![_selectedIndex],
               _onEditFileRequested,
               _onEditFileRequested,
               widget.config.mode == DetailPageMode.minimalistic,
               widget.config.mode == DetailPageMode.minimalistic,
+              onFileRemoved: _onFileRemoved,
+              userID: Configuration.instance.getUserID(),
               key: _bottomBarKey,
               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.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/ignored_file.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/selected_files.dart';
 import 'package:photos/models/trash_file.dart';
 import 'package:photos/models/trash_file.dart';
 import 'package:photos/services/collections_service.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/services/local_sync_service.dart';
 import 'package:photos/ui/collection_action_sheet.dart';
 import 'package:photos/ui/collection_action_sheet.dart';
 import 'package:photos/ui/common/progress_dialog.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/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/dialog_util.dart';
 import 'package:photos/utils/file_util.dart';
 import 'package:photos/utils/file_util.dart';
+import "package:photos/utils/magic_util.dart";
 import 'package:photos/utils/toast_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 
 class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
 class FadingAppBar extends StatefulWidget implements PreferredSizeWidget {
@@ -148,22 +146,22 @@ class FadingAppBarState extends State<FadingAppBar> {
             );
             );
           }
           }
           // options for files owned by the user
           // options for files owned by the user
-          if (isOwnedByUser) {
+          if (isOwnedByUser && !isFileHidden) {
+            final bool isArchived =
+                widget.file.magicMetadata.visibility == visibilityArchive;
             items.add(
             items.add(
               PopupMenuItem(
               PopupMenuItem(
                 value: 2,
                 value: 2,
                 child: Row(
                 child: Row(
                   children: [
                   children: [
                     Icon(
                     Icon(
-                      Platform.isAndroid
-                          ? Icons.delete_outline
-                          : CupertinoIcons.delete,
+                      isArchived ? Icons.unarchive : Icons.archive_outlined,
                       color: Theme.of(context).iconTheme.color,
                       color: Theme.of(context).iconTheme.color,
                     ),
                     ),
                     const Padding(
                     const Padding(
                       padding: EdgeInsets.all(8),
                       padding: EdgeInsets.all(8),
                     ),
                     ),
-                    const Text("Delete"),
+                    Text(isArchived ? "Unarchive" : "Archive"),
                   ],
                   ],
                 ),
                 ),
               ),
               ),
@@ -235,7 +233,7 @@ class FadingAppBarState extends State<FadingAppBar> {
           if (value == 1) {
           if (value == 1) {
             _download(widget.file);
             _download(widget.file);
           } else if (value == 2) {
           } else if (value == 2) {
-            await _showSingleFileDeleteSheet(widget.file);
+            await _toggleFileArchiveStatus(widget.file);
           } else if (value == 3) {
           } else if (value == 3) {
             _setAs(widget.file);
             _setAs(widget.file);
           } else if (value == 4) {
           } 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/cupertino.dart';
 import 'package:flutter/material.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.dart';
 import 'package:photos/models/file_type.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/selected_files.dart';
 import 'package:photos/models/trash_file.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/colors.dart';
 import 'package:photos/theme/ente_theme.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/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/delete_file_util.dart';
-import 'package:photos/utils/magic_util.dart';
 import 'package:photos/utils/share_util.dart';
 import 'package:photos/utils/share_util.dart';
 
 
 class FadingBottomBar extends StatefulWidget {
 class FadingBottomBar extends StatefulWidget {
   final File file;
   final File file;
   final Function(File) onEditRequested;
   final Function(File) onEditRequested;
+  final Function(File) onFileRemoved;
   final bool showOnlyInfoButton;
   final bool showOnlyInfoButton;
+  final int? userID;
 
 
   const FadingBottomBar(
   const FadingBottomBar(
     this.file,
     this.file,
     this.onEditRequested,
     this.onEditRequested,
     this.showOnlyInfoButton, {
     this.showOnlyInfoButton, {
+    required this.onFileRemoved,
+    this.userID,
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -63,6 +62,8 @@ class FadingBottomBarState extends State<FadingBottomBar> {
 
 
   Widget _getBottomBar() {
   Widget _getBottomBar() {
     final List<Widget> children = [];
     final List<Widget> children = [];
+    final bool isOwnedByUser =
+        widget.file.ownerID == null || widget.file.ownerID == widget.userID;
     children.add(
     children.add(
       Tooltip(
       Tooltip(
         message: "Info",
         message: "Info",
@@ -88,15 +89,7 @@ class FadingBottomBarState extends State<FadingBottomBar> {
     if (widget.file is TrashFile) {
     if (widget.file is TrashFile) {
       _addTrashOptions(children);
       _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.showOnlyInfoButton && widget.file is! TrashFile) {
       if (widget.file.fileType == FileType.image ||
       if (widget.file.fileType == FileType.image ||
           widget.file.fileType == FileType.livePhoto) {
           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(
         children.add(
           Tooltip(
           Tooltip(
-            message: isArchived ? "Unarchive" : "Archive",
+            message: "Delete",
             child: Padding(
             child: Padding(
               padding: const EdgeInsets.only(top: 12, bottom: 12),
               padding: const EdgeInsets.only(top: 12, bottom: 12),
               child: IconButton(
               child: IconButton(
                 icon: Icon(
                 icon: Icon(
-                  isArchived ? Icons.unarchive : Icons.archive_outlined,
+                  Platform.isAndroid
+                      ? Icons.delete_outline
+                      : CupertinoIcons.delete,
                   color: Colors.white,
                   color: Colors.white,
                 ),
                 ),
                 onPressed: () async {
                 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) {
   void _addTrashOptions(List<Widget> children) {
     children.add(
     children.add(
       Tooltip(
       Tooltip(
@@ -272,20 +268,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
   }
   }
 
 
   Future<void> _displayInfo(File file) async {
   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:collection/collection.dart' show IterableExtension;
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import "package:logging/logging.dart";
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/events/files_updated_event.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/gallery_type.dart';
 import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/services/collections_service.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/actions/file_selection_overlay_bar.dart';
 import "package:photos/ui/viewer/gallery/empty_state.dart";
 import "package:photos/ui/viewer/gallery/empty_state.dart";
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
@@ -22,7 +19,6 @@ class ArchivePage extends StatelessWidget {
   final GalleryType appBarType;
   final GalleryType appBarType;
   final GalleryType overlayType;
   final GalleryType overlayType;
   final _selectedFiles = SelectedFiles();
   final _selectedFiles = SelectedFiles();
-  final Logger _logger = Logger("ArchivePage");
 
 
   ArchivePage({
   ArchivePage({
     this.tagPrefix = "archived_page",
     this.tagPrefix = "archived_page",
@@ -70,43 +66,8 @@ class ArchivePage extends StatelessWidget {
       emptyState: const EmptyState(
       emptyState: const EmptyState(
         text: "You don't have any archived items.",
         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(
     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.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
 import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
 
 
-class CollectionPage extends StatefulWidget {
+class CollectionPage extends StatelessWidget {
   final CollectionWithThumbnail c;
   final CollectionWithThumbnail c;
   final String tagPrefix;
   final String tagPrefix;
   final GalleryType appBarType;
   final GalleryType appBarType;
   final bool hasVerifiedLock;
   final bool hasVerifiedLock;
 
 
-  const CollectionPage(
+  CollectionPage(
     this.c, {
     this.c, {
     this.tagPrefix = "collection",
     this.tagPrefix = "collection",
     this.appBarType = GalleryType.ownedCollection,
     this.appBarType = GalleryType.ownedCollection,
@@ -30,42 +30,24 @@ class CollectionPage extends StatefulWidget {
     Key? key,
     Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
-  @override
-  State<CollectionPage> createState() => _CollectionPageState();
-}
-
-class _CollectionPageState extends State<CollectionPage> {
   final _selectedFiles = SelectedFiles();
   final _selectedFiles = SelectedFiles();
 
 
   final GlobalKey shareButtonKey = GlobalKey();
   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
   @override
   Widget build(Object context) {
   Widget build(Object context) {
-    if (widget.hasVerifiedLock == false && widget.c.collection.isHidden()) {
+    if (hasVerifiedLock == false && c.collection.isHidden()) {
       return const EmptyState();
       return const EmptyState();
     }
     }
 
 
-    final appBarTypeValue = _getGalleryType(widget.c.collection);
+    final appBarTypeValue = _getGalleryType(c.collection);
     final List<File>? initialFiles =
     final List<File>? initialFiles =
-        widget.c.thumbnail != null ? [widget.c.thumbnail!] : null;
+        c.thumbnail != null ? [c.thumbnail!] : null;
     final gallery = Gallery(
     final gallery = Gallery(
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) async {
         final FileLoadResult result =
         final FileLoadResult result =
             await FilesDB.instance.getFilesInCollection(
             await FilesDB.instance.getFilesInCollection(
-          widget.c.collection.id,
+          c.collection.id,
           creationStartTime,
           creationStartTime,
           creationEndTime,
           creationEndTime,
           limit: limit,
           limit: limit,
@@ -82,25 +64,25 @@ class _CollectionPageState extends State<CollectionPage> {
       },
       },
       reloadEvent: Bus.instance
       reloadEvent: Bus.instance
           .on<CollectionUpdatedEvent>()
           .on<CollectionUpdatedEvent>()
-          .where((event) => event.collectionID == widget.c.collection.id),
+          .where((event) => event.collectionID == c.collection.id),
       removalEventTypes: const {
       removalEventTypes: const {
         EventType.deletedFromRemote,
         EventType.deletedFromRemote,
         EventType.deletedFromEverywhere,
         EventType.deletedFromEverywhere,
         EventType.hide,
         EventType.hide,
       },
       },
-      tagPrefix: widget.tagPrefix,
+      tagPrefix: tagPrefix,
       selectedFiles: _selectedFiles,
       selectedFiles: _selectedFiles,
       initialFiles: initialFiles,
       initialFiles: initialFiles,
-      albumName: widget.c.collection.name,
+      albumName: c.collection.name,
     );
     );
     return Scaffold(
     return Scaffold(
       appBar: PreferredSize(
       appBar: PreferredSize(
         preferredSize: const Size.fromHeight(50.0),
         preferredSize: const Size.fromHeight(50.0),
         child: GalleryAppBarWidget(
         child: GalleryAppBarWidget(
           appBarTypeValue,
           appBarTypeValue,
-          widget.c.collection.name,
+          c.collection.name,
           _selectedFiles,
           _selectedFiles,
-          collection: widget.c.collection,
+          collection: c.collection,
         ),
         ),
       ),
       ),
       body: Stack(
       body: Stack(
@@ -110,7 +92,7 @@ class _CollectionPageState extends State<CollectionPage> {
           FileSelectionOverlayBar(
           FileSelectionOverlayBar(
             appBarTypeValue,
             appBarTypeValue,
             _selectedFiles,
             _selectedFiles,
-            collection: widget.c.collection,
+            collection: c.collection,
           )
           )
         ],
         ],
       ),
       ),
@@ -129,12 +111,6 @@ class _CollectionPageState extends State<CollectionPage> {
     } else if (c.type == CollectionType.favorites) {
     } else if (c.type == CollectionType.favorites) {
       return GalleryType.favorite;
       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/ui/viewer/gallery/gallery_app_bar_widget.dart';
 import 'package:photos/utils/delete_file_util.dart';
 import 'package:photos/utils/delete_file_util.dart';
 
 
-class TrashPage extends StatefulWidget {
+class TrashPage extends StatelessWidget {
   final String tagPrefix;
   final String tagPrefix;
   final GalleryType appBarType;
   final GalleryType appBarType;
   final GalleryType overlayType;
   final GalleryType overlayType;
@@ -26,30 +26,9 @@ class TrashPage extends StatefulWidget {
     Key? key,
     Key? key,
   }) : super(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
   @override
   Widget build(Object context) {
   Widget build(Object context) {
-    final bool filesAreSelected = widget._selectedFiles.files.isNotEmpty;
+    final bool filesAreSelected = _selectedFiles.files.isNotEmpty;
 
 
     final gallery = Gallery(
     final gallery = Gallery(
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
       asyncLoader: (creationStartTime, creationEndTime, {limit, asc}) {
@@ -70,8 +49,8 @@ class _TrashPageState extends State<TrashPage> {
       forceReloadEvents: [
       forceReloadEvents: [
         Bus.instance.on<ForceReloadTrashPageEvent>(),
         Bus.instance.on<ForceReloadTrashPageEvent>(),
       ],
       ],
-      tagPrefix: widget.tagPrefix,
-      selectedFiles: widget._selectedFiles,
+      tagPrefix: tagPrefix,
+      selectedFiles: _selectedFiles,
       header: _headerWidget(),
       header: _headerWidget(),
       initialFiles: null,
       initialFiles: null,
     );
     );
@@ -80,9 +59,9 @@ class _TrashPageState extends State<TrashPage> {
       appBar: PreferredSize(
       appBar: PreferredSize(
         preferredSize: const Size.fromHeight(50.0),
         preferredSize: const Size.fromHeight(50.0),
         child: GalleryAppBarWidget(
         child: GalleryAppBarWidget(
-          widget.appBarType,
+          appBarType,
           "Trash",
           "Trash",
-          widget._selectedFiles,
+          _selectedFiles,
         ),
         ),
       ),
       ),
       body: Stack(
       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/db/files_db.dart';
 import 'package:photos/events/collection_updated_event.dart';
 import 'package:photos/events/collection_updated_event.dart';
 import 'package:photos/events/files_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/events/local_photos_updated_event.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/selected_files.dart';
 import 'package:photos/models/selected_files.dart';
@@ -264,6 +265,9 @@ Future<bool> deleteFromTrash(BuildContext context, List<File> files) async {
             source: "deleteFromTrash",
             source: "deleteFromTrash",
           ),
           ),
         );
         );
+        //the FilesUpdateEvent is not reloading trash on premanently removing
+        //files, so need to fire ForceReloadTrashPageEvent
+        Bus.instance.fire(ForceReloadTrashPageEvent());
       } catch (e, s) {
       } catch (e, s) {
         _logger.info("failed to delete from trash", e, s);
         _logger.info("failed to delete from trash", e, s);
         rethrow;
         rethrow;

+ 3 - 2
lib/utils/dialog_util.dart

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

+ 6 - 4
lib/utils/file_uploader.dart

@@ -6,7 +6,7 @@ import 'dart:math';
 import 'dart:typed_data';
 import 'dart:typed_data';
 
 
 import 'package:collection/collection.dart';
 import 'package:collection/collection.dart';
-import 'package:connectivity/connectivity.dart';
+import 'package:connectivity_plus/connectivity_plus.dart';
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
@@ -282,9 +282,11 @@ class FileUploader {
       return;
       return;
     }
     }
     final connectivityResult = await (Connectivity().checkConnectivity());
     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) {
     if (!canUploadUnderCurrentNetworkConditions) {
       throw WiFiUnavailableError();
       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"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "0.6.0"
     version: "0.6.0"
-  connectivity:
+  connectivity_plus:
     dependency: "direct main"
     dependency: "direct main"
     description:
     description:
-      name: connectivity
-      sha256: a8e91263cf3e25fb5cc95e19dfde4999e32a648ac3b9e8a558a28165731678f8
+      name: connectivity_plus
+      sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     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
     dependency: transitive
     description:
     description:
-      name: connectivity_platform_interface
-      sha256: "2d82e942df9d49f29a24bb07fb5ce085d4a53e47818c62364d2b6deb9e0d7a8e"
+      name: connectivity_plus_platform_interface
+      sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
-    version: "2.0.1"
+    version: "1.2.4"
   convert:
   convert:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -493,6 +477,14 @@ packages:
     description: flutter
     description: flutter
     source: sdk
     source: sdk
     version: "0.0.0"
     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:
   flutter_blurhash:
     dependency: transitive
     dependency: transitive
     description:
     description:
@@ -1005,6 +997,14 @@ packages:
       url: "https://pub.dev"
       url: "https://pub.dev"
     source: hosted
     source: hosted
     version: "1.0.0"
     version: "1.0.0"
+  nm:
+    dependency: transitive
+    description:
+      name: nm
+      sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.5.0"
   node_preamble:
   node_preamble:
     dependency: transitive
     dependency: transitive
     description:
     description:

+ 12 - 3
pubspec.yaml

@@ -12,7 +12,7 @@ description: ente photos application
 # Read more about iOS versioning at
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 
 
-version: 0.7.26+426
+version: 0.7.27+427
 
 
 environment:
 environment:
   sdk: '>=2.17.0 <3.0.0'
   sdk: '>=2.17.0 <3.0.0'
@@ -30,7 +30,7 @@ dependencies:
   collection: # dart
   collection: # dart
   computer: ^2.0.0
   computer: ^2.0.0
   confetti: ^0.6.0
   confetti: ^0.6.0
-  connectivity: ^3.0.3
+  connectivity_plus: ^3.0.3
   cupertino_icons: ^1.0.0
   cupertino_icons: ^1.0.0
   device_info: ^2.0.2
   device_info: ^2.0.2
   dio: ^4.0.6
   dio: ^4.0.6
@@ -49,6 +49,7 @@ dependencies:
   fk_user_agent: ^2.0.1
   fk_user_agent: ^2.0.1
   flutter:
   flutter:
     sdk: flutter
     sdk: flutter
+  flutter_animate: ^4.1.0
   flutter_cache_manager: ^3.3.0
   flutter_cache_manager: ^3.3.0
   flutter_datetime_picker: ^1.5.1
   flutter_datetime_picker: ^1.5.1
   flutter_easyloading: ^3.0.0
   flutter_easyloading: ^3.0.0
@@ -76,7 +77,7 @@ dependencies:
   local_auth: ^1.1.5
   local_auth: ^1.1.5
   logging: ^1.0.1
   logging: ^1.0.1
   lottie: ^1.2.2
   lottie: ^1.2.2
-  media_extension: ^1.0.0
+  media_extension: ^1.0.1
   modal_bottom_sheet: ^3.0.0-pre
   modal_bottom_sheet: ^3.0.0-pre
   motionphoto:
   motionphoto:
     git: "https://github.com/ente-io/motionphoto.git"
     git: "https://github.com/ente-io/motionphoto.git"
@@ -147,6 +148,14 @@ flutter_native_splash:
   android_fullscreen: true
   android_fullscreen: true
   android_gravity: center
   android_gravity: center
   ios_content_mode: 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
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 # following page: https://dart.dev/tools/pub/pubspec

+ 7 - 9
run.sh

@@ -2,14 +2,12 @@
 
 
 FLUTTER_RUN="flutter run --flavor dev "
 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"
 echo "Running: $FLUTTER_RUN"
 $FLUTTER_RUN
 $FLUTTER_RUN