Browse Source

Resolved merge conflicts

ashilkn 2 years ago
parent
commit
caeb3ef84e
100 changed files with 1444 additions and 1404 deletions
  1. 4 0
      android/app/build.gradle
  2. 10 0
      android/app/src/dev/AndroidManifest.xml
  3. 4 0
      android/app/src/dev/res/values/strings.xml
  4. 9 18
      ios/Runner.xcodeproj/project.pbxproj
  5. 2 2
      lib/app.dart
  6. 3 3
      lib/core/configuration.dart
  7. 45 41
      lib/core/error-reporting/super_logging.dart
  8. 11 13
      lib/core/error-reporting/tunneled_transport.dart
  9. 25 23
      lib/db/device_files_db.dart
  10. 4 5
      lib/db/file_updation_db.dart
  11. 70 63
      lib/db/files_db.dart
  12. 2 4
      lib/db/ignored_files_db.dart
  13. 4 5
      lib/db/memories_db.dart
  14. 12 14
      lib/db/trash_db.dart
  15. 1 3
      lib/events/collection_updated_event.dart
  16. 0 2
      lib/events/files_updated_event.dart
  17. 3 3
      lib/main.dart
  18. 4 2
      lib/models/collection.dart
  19. 3 4
      lib/models/duplicate_files.dart
  20. 1 4
      lib/models/file.dart
  21. 2 2
      lib/models/key_attributes.dart
  22. 1 3
      lib/services/billing_service.dart
  23. 158 156
      lib/services/collections_service.dart
  24. 7 8
      lib/services/deduplication_service.dart
  25. 28 32
      lib/services/favorites_service.dart
  26. 34 30
      lib/services/file_magic_service.dart
  27. 4 5
      lib/services/hidden_service.dart
  28. 21 18
      lib/services/ignored_files_service.dart
  29. 14 14
      lib/services/local/local_sync_util.dart
  30. 5 7
      lib/services/local_authentication_service.dart
  31. 23 23
      lib/services/local_file_update_service.dart
  32. 54 17
      lib/services/local_sync_service.dart
  33. 6 8
      lib/services/memories_service.dart
  34. 10 8
      lib/services/push_service.dart
  35. 40 36
      lib/services/remote_sync_service.dart
  36. 20 21
      lib/services/search_service.dart
  37. 14 14
      lib/services/sync_service.dart
  38. 4 6
      lib/services/trash_sync_service.dart
  39. 6 8
      lib/services/update_service.dart
  40. 36 37
      lib/services/user_service.dart
  41. 1 1
      lib/ui/account/change_email_dialog.dart
  42. 5 7
      lib/ui/account/delete_account_page.dart
  43. 15 17
      lib/ui/account/email_entry_page.dart
  44. 10 10
      lib/ui/account/login_page.dart
  45. 6 9
      lib/ui/account/ott_verification_page.dart
  46. 15 17
      lib/ui/account/password_entry_page.dart
  47. 6 15
      lib/ui/account/password_reentry_page.dart
  48. 13 13
      lib/ui/account/recovery_key_page.dart
  49. 4 4
      lib/ui/account/recovery_page.dart
  50. 0 1
      lib/ui/actions/collection/collection_file_actions.dart
  51. 1 2
      lib/ui/actions/collection/collection_sharing_actions.dart
  52. 18 20
      lib/ui/backup_folder_selection_page.dart
  53. 1 1
      lib/ui/collections/archived_collections_button_widget.dart
  54. 7 7
      lib/ui/collections/device_folders_grid_view_widget.dart
  55. 3 3
      lib/ui/collections/hidden_collections_button_widget.dart
  56. 6 6
      lib/ui/collections/remote_collections_grid_view_widget.dart
  57. 23 15
      lib/ui/collections_gallery_widget.dart
  58. 2 2
      lib/ui/common/DividerWithPadding.dart
  59. 3 3
      lib/ui/common/bottom_shadow.dart
  60. 4 4
      lib/ui/common/dialogs.dart
  61. 15 15
      lib/ui/common/dynamic_fab.dart
  62. 5 5
      lib/ui/common/gradient_button.dart
  63. 3 3
      lib/ui/common/linear_progress_dialog.dart
  64. 36 36
      lib/ui/common/progress_dialog.dart
  65. 7 7
      lib/ui/common/rename_dialog.dart
  66. 2 2
      lib/ui/common/web_page.dart
  67. 2 3
      lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart
  68. 33 0
      lib/ui/components/button_widget.dart
  69. 0 1
      lib/ui/components/keyboard/keyboard_top_button.dart
  70. 26 24
      lib/ui/create_collection_page.dart
  71. 24 28
      lib/ui/extents_page_view.dart
  72. 4 4
      lib/ui/home/header_error_widget.dart
  73. 0 1
      lib/ui/home/home_bottom_nav_bar.dart
  74. 6 7
      lib/ui/home/home_gallery_widget.dart
  75. 16 16
      lib/ui/home/memories_widget.dart
  76. 29 31
      lib/ui/home/status_bar_widget.dart
  77. 18 18
      lib/ui/home_widget.dart
  78. 14 16
      lib/ui/huge_listview/draggable_scrollbar.dart
  79. 15 15
      lib/ui/huge_listview/huge_listview.dart
  80. 39 40
      lib/ui/huge_listview/lazy_loading_gallery.dart
  81. 14 14
      lib/ui/huge_listview/scroll_bar_thumb.dart
  82. 5 5
      lib/ui/lifecycle_event_handler.dart
  83. 6 6
      lib/ui/loading_photos_widget.dart
  84. 93 93
      lib/ui/nav_bar.dart
  85. 2 2
      lib/ui/notification/update/change_log_page.dart
  86. 12 16
      lib/ui/payment/billing_questions_widget.dart
  87. 27 28
      lib/ui/payment/payment_web_page.dart
  88. 30 32
      lib/ui/payment/stripe_subscription_page.dart
  89. 14 14
      lib/ui/payment/subscription_common_widgets.dart
  90. 33 35
      lib/ui/payment/subscription_page.dart
  91. 7 7
      lib/ui/payment/subscription_plan_widget.dart
  92. 3 1
      lib/ui/settings/about_section_widget.dart
  93. 15 15
      lib/ui/settings/app_update_dialog.dart
  94. 4 4
      lib/ui/settings/backup_section_widget.dart
  95. 0 2
      lib/ui/settings/settings_title_bar_widget.dart
  96. 0 1
      lib/ui/settings/social_section_widget.dart
  97. 4 4
      lib/ui/settings/theme_switch_widget.dart
  98. 5 5
      lib/ui/settings_page.dart
  99. 28 28
      lib/ui/shared_collections_gallery.dart
  100. 1 1
      lib/ui/sharing/add_partipant_page.dart

+ 4 - 0
android/app/build.gradle

@@ -69,6 +69,10 @@ android {
             dimension "default"
             dimension "default"
             applicationIdSuffix ".independent"
             applicationIdSuffix ".independent"
         }
         }
+        dev {
+            dimension "default"
+            applicationIdSuffix ".dev"
+        }
         playstore {
         playstore {
             dimension "default"
             dimension "default"
         }
         }

+ 10 - 0
android/app/src/dev/AndroidManifest.xml

@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.ente.photos">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+</manifest>

+ 4 - 0
android/app/src/dev/res/values/strings.xml

@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">ente dev</string>
+    <string name="backup">backup dev</string>
+</resources>

+ 9 - 18
ios/Runner.xcodeproj/project.pbxproj

@@ -16,7 +16,7 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
-		DA6BE5E826B3BC8600656280 /* BuildFile in Resources */ = {isa = PBXBuildFile; };
+		DA6BE5E826B3BC8600656280 /* (null) in Resources */ = {isa = PBXBuildFile; };
 /* End PBXBuildFile section */
 /* End PBXBuildFile section */
 
 
 /* Begin PBXCopyFilesBuildPhase section */
 /* Begin PBXCopyFilesBuildPhase section */
@@ -180,7 +180,7 @@
 				TargetAttributes = {
 				TargetAttributes = {
 					97C146ED1CF9000F007C117D = {
 					97C146ED1CF9000F007C117D = {
 						CreatedOnToolsVersion = 7.3.1;
 						CreatedOnToolsVersion = 7.3.1;
-						DevelopmentTeam = 2BUSYC7FN9;
+						DevelopmentTeam = 6Z68YJY9Q2;
 						LastSwiftMigration = 1100;
 						LastSwiftMigration = 1100;
 						ProvisioningStyle = Automatic;
 						ProvisioningStyle = Automatic;
 					};
 					};
@@ -213,7 +213,7 @@
 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
 				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
 				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
 				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
-				DA6BE5E826B3BC8600656280 /* BuildFile in Resources */,
+				DA6BE5E826B3BC8600656280 /* (null) in Resources */,
 				277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
 				277218A0270F596900FFE3CC /* GoogleService-Info.plist in Resources */,
 			);
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			runOnlyForDeploymentPostprocessing = 0;
@@ -494,17 +494,14 @@
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 11;
 				CURRENT_PROJECT_VERSION = 11;
-				DEVELOPMENT_TEAM = 2BUSYC7FN9;
+				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
@@ -654,17 +651,14 @@
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 11;
 				CURRENT_PROJECT_VERSION = 11;
-				DEVELOPMENT_TEAM = 2BUSYC7FN9;
+				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
@@ -691,17 +685,14 @@
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_IDENTITY = "Apple Development";
 				CODE_SIGN_STYLE = Automatic;
 				CODE_SIGN_STYLE = Automatic;
 				CURRENT_PROJECT_VERSION = 11;
 				CURRENT_PROJECT_VERSION = 11;
-				DEVELOPMENT_TEAM = 2BUSYC7FN9;
+				DEVELOPMENT_TEAM = 6Z68YJY9Q2;
 				ENABLE_BITCODE = NO;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",
 				);
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = (
-					"$(inherited)",
-					"@executable_path/Frameworks",
-				);
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
 				LIBRARY_SEARCH_PATHS = (
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 					"$(PROJECT_DIR)/Flutter",

+ 2 - 2
lib/app.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:io';
 import 'dart:io';
 
 
@@ -23,7 +23,7 @@ class EnteApp extends StatefulWidget {
   const EnteApp(
   const EnteApp(
     this.runBackgroundTask,
     this.runBackgroundTask,
     this.killBackgroundTask, {
     this.killBackgroundTask, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override

+ 3 - 3
lib/core/configuration.dart

@@ -525,9 +525,9 @@ class Configuration {
   Uint8List getRecoveryKey() {
   Uint8List getRecoveryKey() {
     final keyAttributes = getKeyAttributes()!;
     final keyAttributes = getKeyAttributes()!;
     return CryptoUtil.decryptSync(
     return CryptoUtil.decryptSync(
-      Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey),
+      Sodium.base642bin(keyAttributes.recoveryKeyEncryptedWithMasterKey!),
       getKey(),
       getKey(),
-      Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce),
+      Sodium.base642bin(keyAttributes.recoveryKeyDecryptionNonce!),
     );
     );
   }
   }
 
 
@@ -614,7 +614,7 @@ class Configuration {
     return _preferences.setBool(keyShouldHideFromRecents, value);
     return _preferences.setBool(keyShouldHideFromRecents, value);
   }
   }
 
 
-  void setVolatilePassword(String volatilePassword) {
+  void setVolatilePassword(String? volatilePassword) {
     _volatilePassword = volatilePassword;
     _volatilePassword = volatilePassword;
   }
   }
 
 

+ 45 - 41
lib/core/error-reporting/super_logging.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 library super_logging;
 library super_logging;
 
 
 import 'dart:async';
 import 'dart:async';
@@ -38,7 +36,7 @@ extension SuperString on String {
 }
 }
 
 
 extension SuperLogRecord on LogRecord {
 extension SuperLogRecord on LogRecord {
-  String toPrettyString([String extraLines]) {
+  String toPrettyString([String? extraLines]) {
     final header = "[$loggerName] [$level] [$time]";
     final header = "[$loggerName] [$level] [$time]";
 
 
     var msg = "$header $message";
     var msg = "$header $message";
@@ -76,9 +74,9 @@ class LogConfig {
   /// ```
   /// ```
   ///
   ///
   /// If this is [null], Sentry logger is completely disabled (default).
   /// If this is [null], Sentry logger is completely disabled (default).
-  String sentryDsn;
+  String? sentryDsn;
 
 
-  String tunnel;
+  String? tunnel;
 
 
   /// A built-in retry mechanism for sending errors to sentry.
   /// A built-in retry mechanism for sending errors to sentry.
   ///
   ///
@@ -95,7 +93,7 @@ class LogConfig {
   /// A non-empty string will be treated as an explicit path to a directory.
   /// A non-empty string will be treated as an explicit path to a directory.
   ///
   ///
   /// The chosen directory can be accessed using [SuperLogging.logFile.parent].
   /// The chosen directory can be accessed using [SuperLogging.logFile.parent].
-  String logDirPath;
+  String? logDirPath;
 
 
   /// The maximum number of log files inside [logDirPath].
   /// The maximum number of log files inside [logDirPath].
   ///
   ///
@@ -113,12 +111,12 @@ class LogConfig {
   /// any uncaught errors during its execution will be reported.
   /// any uncaught errors during its execution will be reported.
   ///
   ///
   /// Works by using [FlutterError.onError] and [runZoned].
   /// Works by using [FlutterError.onError] and [runZoned].
-  FutureOrVoidCallback body;
+  FutureOrVoidCallback? body;
 
 
   /// The date format for storing log files.
   /// The date format for storing log files.
   ///
   ///
   /// `DateFormat('y-M-d')` by default.
   /// `DateFormat('y-M-d')` by default.
-  DateFormat dateFmt;
+  DateFormat? dateFmt;
 
 
   String prefix;
   String prefix;
 
 
@@ -142,24 +140,24 @@ class SuperLogging {
   static final $ = Logger('ente_logging');
   static final $ = Logger('ente_logging');
 
 
   /// The current super logging configuration
   /// The current super logging configuration
-  static LogConfig config;
+  static late LogConfig config;
 
 
-  static Future<void> main([LogConfig config]) async {
-    config ??= LogConfig();
-    SuperLogging.config = config;
+  static Future<void> main([LogConfig? appConfig]) async {
+    appConfig ??= LogConfig();
+    SuperLogging.config = appConfig;
 
 
     WidgetsFlutterBinding.ensureInitialized();
     WidgetsFlutterBinding.ensureInitialized();
 
 
     appVersion ??= await getAppVersion();
     appVersion ??= await getAppVersion();
     final isFDroidClient = await isFDroidBuild();
     final isFDroidClient = await isFDroidBuild();
     if (isFDroidClient) {
     if (isFDroidClient) {
-      config.sentryDsn = null;
-      config.tunnel = null;
+      appConfig.sentryDsn = null;
+      appConfig.tunnel = null;
     }
     }
 
 
-    final enable = config.enableInDebugMode || kReleaseMode;
-    sentryIsEnabled = enable && config.sentryDsn != null && !isFDroidClient;
-    fileIsEnabled = enable && config.logDirPath != null;
+    final enable = appConfig.enableInDebugMode || kReleaseMode;
+    sentryIsEnabled = enable && appConfig.sentryDsn != null && !isFDroidClient;
+    fileIsEnabled = enable && appConfig.logDirPath != null;
 
 
     if (fileIsEnabled) {
     if (fileIsEnabled) {
       await setupLogDir();
       await setupLogDir();
@@ -175,7 +173,7 @@ class SuperLogging {
       assert(
       assert(
         sentryIsEnabled == false,
         sentryIsEnabled == false,
         "sentry dsn should be disabled for "
         "sentry dsn should be disabled for "
-        "f-droid config  ${config.sentryDsn} & ${config.tunnel}",
+        "f-droid config  ${appConfig.sentryDsn} & ${appConfig.tunnel}",
       );
       );
     }
     }
 
 
@@ -183,39 +181,42 @@ class SuperLogging {
       $.info("detected debug mode; sentry & file logging disabled.");
       $.info("detected debug mode; sentry & file logging disabled.");
     }
     }
     if (fileIsEnabled) {
     if (fileIsEnabled) {
-      $.info("log file for today: $logFile with prefix ${config.prefix}");
+      $.info("log file for today: $logFile with prefix ${appConfig.prefix}");
     }
     }
     if (sentryIsEnabled) {
     if (sentryIsEnabled) {
       $.info("sentry uploader started");
       $.info("sentry uploader started");
     }
     }
 
 
-    if (config.body == null) return;
+    if (appConfig.body == null) return;
 
 
     if (enable && sentryIsEnabled) {
     if (enable && sentryIsEnabled) {
       await SentryFlutter.init(
       await SentryFlutter.init(
         (options) {
         (options) {
-          options.dsn = config.sentryDsn;
+          options.dsn = appConfig!.sentryDsn;
           options.httpClient = http.Client();
           options.httpClient = http.Client();
-          if (config.tunnel != null) {
+          if (appConfig.tunnel != null) {
             options.transport =
             options.transport =
-                TunneledTransport(Uri.parse(config.tunnel), options);
+                TunneledTransport(Uri.parse(appConfig.tunnel!), options);
           }
           }
         },
         },
-        appRunner: () => config.body(),
+        appRunner: () => appConfig!.body!(),
       );
       );
     } else {
     } else {
-      await config.body();
+      await appConfig.body!();
     }
     }
   }
   }
 
 
   static void setUserID(String userID) async {
   static void setUserID(String userID) async {
-    if (config?.sentryDsn != null) {
+    if (config.sentryDsn != null) {
       Sentry.configureScope((scope) => scope.user = SentryUser(id: userID));
       Sentry.configureScope((scope) => scope.user = SentryUser(id: userID));
       $.info("setting sentry user ID to: $userID");
       $.info("setting sentry user ID to: $userID");
     }
     }
   }
   }
 
 
-  static Future<void> _sendErrorToSentry(Object error, StackTrace stack) async {
+  static Future<void> _sendErrorToSentry(
+    Object error,
+    StackTrace? stack,
+  ) async {
     try {
     try {
       await Sentry.captureException(
       await Sentry.captureException(
         error,
         error,
@@ -231,14 +232,14 @@ class SuperLogging {
 
 
   static Future onLogRecord(LogRecord rec) async {
   static Future onLogRecord(LogRecord rec) async {
     // log misc info if it changed
     // log misc info if it changed
-    String extraLines = "app version: '$appVersion'\n";
+    String? extraLines = "app version: '$appVersion'\n";
     if (extraLines != _lastExtraLines) {
     if (extraLines != _lastExtraLines) {
       _lastExtraLines = extraLines;
       _lastExtraLines = extraLines;
     } else {
     } else {
       extraLines = null;
       extraLines = null;
     }
     }
 
 
-    final str = config.prefix + " " + rec.toPrettyString(extraLines);
+    final str = (config.prefix) + " " + rec.toPrettyString(extraLines);
 
 
     // write to stdout
     // write to stdout
     printLog(str);
     printLog(str);
@@ -253,7 +254,7 @@ class SuperLogging {
 
 
     // add error to sentry queue
     // add error to sentry queue
     if (sentryIsEnabled && rec.error != null) {
     if (sentryIsEnabled && rec.error != null) {
-      _sendErrorToSentry(rec.error, null);
+      _sendErrorToSentry(rec.error!, null);
     }
     }
   }
   }
 
 
@@ -261,12 +262,12 @@ class SuperLogging {
   static bool isFlushing = false;
   static bool isFlushing = false;
 
 
   static void flushQueue() async {
   static void flushQueue() async {
-    if (isFlushing) {
+    if (isFlushing || logFile == null) {
       return;
       return;
     }
     }
     isFlushing = true;
     isFlushing = true;
     final entry = fileQueueEntries.removeFirst();
     final entry = fileQueueEntries.removeFirst();
-    await logFile.writeAsString(entry, mode: FileMode.append, flush: true);
+    await logFile!.writeAsString(entry, mode: FileMode.append, flush: true);
     isFlushing = false;
     isFlushing = false;
     if (fileQueueEntries.isNotEmpty) {
     if (fileQueueEntries.isNotEmpty) {
       flushQueue();
       flushQueue();
@@ -285,7 +286,7 @@ class SuperLogging {
   static final sentryQueueControl = StreamController<Error>();
   static final sentryQueueControl = StreamController<Error>();
 
 
   /// Whether sentry logging is currently enabled or not.
   /// Whether sentry logging is currently enabled or not.
-  static bool sentryIsEnabled;
+  static bool sentryIsEnabled = false;
 
 
   static Future<void> setupSentry() async {
   static Future<void> setupSentry() async {
     await for (final error in sentryQueueControl.stream.asBroadcastStream()) {
     await for (final error in sentryQueueControl.stream.asBroadcastStream()) {
@@ -308,18 +309,18 @@ class SuperLogging {
   }
   }
 
 
   /// The log file currently in use.
   /// The log file currently in use.
-  static File logFile;
+  static File? logFile;
 
 
   /// Whether file logging is currently enabled or not.
   /// Whether file logging is currently enabled or not.
-  static bool fileIsEnabled;
+  static bool fileIsEnabled = false;
 
 
   static Future<void> setupLogDir() async {
   static Future<void> setupLogDir() async {
     var dirPath = config.logDirPath;
     var dirPath = config.logDirPath;
 
 
     // choose [logDir]
     // choose [logDir]
-    if (dirPath.isEmpty) {
+    if (dirPath == null || dirPath.isEmpty) {
       final root = await getExternalStorageDirectory();
       final root = await getExternalStorageDirectory();
-      dirPath = '${root.path}/logs';
+      dirPath = '${root!.path}/logs';
     }
     }
 
 
     // create [logDir]
     // create [logDir]
@@ -332,16 +333,19 @@ class SuperLogging {
     // collect all log files with valid names
     // collect all log files with valid names
     await for (final file in dir.list()) {
     await for (final file in dir.list()) {
       try {
       try {
-        final date = config.dateFmt.parse(basename(file.path));
+        final date = config.dateFmt!.parse(basename(file.path));
         dates[file as File] = date;
         dates[file as File] = date;
         files.add(file);
         files.add(file);
       } on FormatException {}
       } on FormatException {}
     }
     }
+    final nowTime = DateTime.now();
 
 
     // delete old log files, if [maxLogFiles] is exceeded.
     // delete old log files, if [maxLogFiles] is exceeded.
     if (files.length > config.maxLogFiles) {
     if (files.length > config.maxLogFiles) {
       // sort files based on ascending order of date (older first)
       // sort files based on ascending order of date (older first)
-      files.sort((a, b) => dates[a].compareTo(dates[b]));
+      files.sort(
+        (a, b) => (dates[a] ?? nowTime).compareTo((dates[b] ?? nowTime)),
+      );
 
 
       final extra = files.length - config.maxLogFiles;
       final extra = files.length - config.maxLogFiles;
       final toDelete = files.sublist(0, extra);
       final toDelete = files.sublist(0, extra);
@@ -356,13 +360,13 @@ class SuperLogging {
       }
       }
     }
     }
 
 
-    logFile = File("$dirPath/${config.dateFmt.format(DateTime.now())}.txt");
+    logFile = File("$dirPath/${config.dateFmt!.format(DateTime.now())}.txt");
   }
   }
 
 
   /// Current app version, obtained from package_info plugin.
   /// Current app version, obtained from package_info plugin.
   ///
   ///
   /// See: [getAppVersion]
   /// See: [getAppVersion]
-  static String appVersion;
+  static String? appVersion;
 
 
   static Future<String> getAppVersion() async {
   static Future<String> getAppVersion() async {
     final pkgInfo = await PackageInfo.fromPlatform();
     final pkgInfo = await PackageInfo.fromPlatform();

+ 11 - 13
lib/core/error-reporting/tunneled_transport.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:http/http.dart';
 import 'package:http/http.dart';
@@ -10,9 +8,9 @@ class TunneledTransport implements Transport {
   final Uri _tunnel;
   final Uri _tunnel;
   final SentryOptions _options;
   final SentryOptions _options;
 
 
-  final Dsn _dsn;
+  final Dsn? _dsn;
 
 
-  _CredentialBuilder _credentialBuilder;
+  _CredentialBuilder? _credentialBuilder;
 
 
   final Map<String, String> _headers;
   final Map<String, String> _headers;
 
 
@@ -21,7 +19,7 @@ class TunneledTransport implements Transport {
   }
   }
 
 
   TunneledTransport._(this._tunnel, this._options)
   TunneledTransport._(this._tunnel, this._options)
-      : _dsn = Dsn.parse(_options.dsn),
+      : _dsn = _options.dsn != null ? Dsn.parse(_options.dsn!) : null,
         _headers = _buildHeaders(
         _headers = _buildHeaders(
           _options.platformChecker.isWeb,
           _options.platformChecker.isWeb,
           _options.sdk.identifier,
           _options.sdk.identifier,
@@ -34,7 +32,7 @@ class TunneledTransport implements Transport {
   }
   }
 
 
   @override
   @override
-  Future<SentryId> send(SentryEnvelope envelope) async {
+  Future<SentryId?> send(SentryEnvelope envelope) async {
     final streamedRequest = await _createStreamedRequest(envelope);
     final streamedRequest = await _createStreamedRequest(envelope);
     final response = await _options.httpClient
     final response = await _options.httpClient
         .send(streamedRequest)
         .send(streamedRequest)
@@ -74,7 +72,7 @@ class TunneledTransport implements Transport {
         .listen(streamedRequest.sink.add)
         .listen(streamedRequest.sink.add)
         .onDone(streamedRequest.sink.close);
         .onDone(streamedRequest.sink.close);
 
 
-    streamedRequest.headers.addAll(_credentialBuilder.configure(_headers));
+    streamedRequest.headers.addAll(_credentialBuilder!.configure(_headers));
 
 
     return streamedRequest;
     return streamedRequest;
   }
   }
@@ -92,13 +90,13 @@ class _CredentialBuilder {
         _clock = clock;
         _clock = clock;
 
 
   factory _CredentialBuilder(
   factory _CredentialBuilder(
-    Dsn dsn,
+    Dsn? dsn,
     String sdkIdentifier,
     String sdkIdentifier,
     ClockProvider clock,
     ClockProvider clock,
   ) {
   ) {
     final authHeader = _buildAuthHeader(
     final authHeader = _buildAuthHeader(
-      publicKey: dsn.publicKey,
-      secretKey: dsn.secretKey,
+      publicKey: dsn?.publicKey,
+      secretKey: dsn?.secretKey,
       sdkIdentifier: sdkIdentifier,
       sdkIdentifier: sdkIdentifier,
     );
     );
 
 
@@ -106,9 +104,9 @@ class _CredentialBuilder {
   }
   }
 
 
   static String _buildAuthHeader({
   static String _buildAuthHeader({
-    String publicKey,
-    String secretKey,
-    String sdkIdentifier,
+    String? publicKey,
+    String? secretKey,
+    String? sdkIdentifier,
   }) {
   }) {
     var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, '
     var header = 'Sentry sentry_version=7, sentry_client=$sdkIdentifier, '
         'sentry_key=$publicKey';
         'sentry_key=$publicKey';

+ 25 - 23
lib/db/device_files_db.dart

@@ -1,4 +1,4 @@
-// @dart = 2.9
+import 'package:collection/collection.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.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';
@@ -85,7 +85,7 @@ extension DeviceFiles on FilesDB {
       );
       );
       final result = <String, int>{};
       final result = <String, int>{};
       for (final row in rows) {
       for (final row in rows) {
-        result[row['path_id']] = row["count"];
+        result[row['path_id'] as String] = row["count"] as int;
       }
       }
       return result;
       return result;
     } catch (e) {
     } catch (e) {
@@ -102,11 +102,11 @@ extension DeviceFiles on FilesDB {
       );
       );
       final result = <String, Set<String>>{};
       final result = <String, Set<String>>{};
       for (final row in rows) {
       for (final row in rows) {
-        final String pathID = row['path_id'];
+        final String pathID = row['path_id'] as String;
         if (!result.containsKey(pathID)) {
         if (!result.containsKey(pathID)) {
           result[pathID] = <String>{};
           result[pathID] = <String>{};
         }
         }
-        result[pathID].add(row['id']);
+        result[pathID]!.add(row['id'] as String);
       }
       }
       return result;
       return result;
     } catch (e) {
     } catch (e) {
@@ -124,7 +124,7 @@ extension DeviceFiles on FilesDB {
     );
     );
     final Set<String> result = <String>{};
     final Set<String> result = <String>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row['id']);
+      result.add(row['id'] as String);
     }
     }
     return result;
     return result;
   }
   }
@@ -220,8 +220,10 @@ extension DeviceFiles on FilesDB {
       existingPathIds.removeAll(devicePathInfo.map((e) => e.item1.id).toSet());
       existingPathIds.removeAll(devicePathInfo.map((e) => e.item1.id).toSet());
       if (existingPathIds.isNotEmpty) {
       if (existingPathIds.isNotEmpty) {
         hasUpdated = true;
         hasUpdated = true;
-        _logger.info('Deleting non-backed up pathIds from local '
-            '$existingPathIds');
+        _logger.info(
+          'Deleting non-backed up pathIds from local '
+          '$existingPathIds',
+        );
         for (String pathID in existingPathIds) {
         for (String pathID in existingPathIds) {
           // do not delete device collection entries for paths which are
           // do not delete device collection entries for paths which are
           // marked for backup. This is to handle "Free up space"
           // marked for backup. This is to handle "Free up space"
@@ -260,7 +262,7 @@ extension DeviceFiles on FilesDB {
     );
     );
     final Set<int> result = <int>{};
     final Set<int> result = <int>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row['collection_id']);
+      result.add(row['collection_id'] as int);
     }
     }
     return result;
     return result;
   }
   }
@@ -307,8 +309,8 @@ extension DeviceFiles on FilesDB {
     DeviceCollection deviceCollection,
     DeviceCollection deviceCollection,
     int startTime,
     int startTime,
     int endTime, {
     int endTime, {
-    int limit,
-    bool asc,
+    int? limit,
+    bool? asc,
   }) async {
   }) async {
     final db = await database;
     final db = await database;
     final order = (asc ?? false ? 'ASC' : 'DESC');
     final order = (asc ?? false ? 'ASC' : 'DESC');
@@ -348,8 +350,9 @@ extension DeviceFiles on FilesDB {
     final localIDs = <String>{};
     final localIDs = <String>{};
     final uploadedIDs = <int>{};
     final uploadedIDs = <int>{};
     for (final result in results) {
     for (final result in results) {
-      localIDs.add(result[FilesDB.columnLocalID]);
-      uploadedIDs.add(result[FilesDB.columnUploadedFileID]);
+      // FilesDB.[columnLocalID,columnUploadedFileID] is not null check in query
+      localIDs.add(result[FilesDB.columnLocalID] as String);
+      uploadedIDs.add(result[FilesDB.columnUploadedFileID] as int);
     }
     }
     return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
     return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
   }
   }
@@ -378,21 +381,20 @@ extension DeviceFiles on FilesDB {
       final List<DeviceCollection> deviceCollections = [];
       final List<DeviceCollection> deviceCollections = [];
       for (var row in deviceCollectionRows) {
       for (var row in deviceCollectionRows) {
         final DeviceCollection deviceCollection = DeviceCollection(
         final DeviceCollection deviceCollection = DeviceCollection(
-          row["id"],
-          row['name'],
-          count: row['count'],
-          collectionID: row["collection_id"],
-          coverId: row["cover_id"],
+          row["id"] as String,
+          (row['name'] ?? '') as String,
+          count: row['count'] as int,
+          collectionID: (row["collection_id"] ?? -1) as int,
+          coverId: row["cover_id"] as String?,
           shouldBackup: (row["should_backup"] ?? _sqlBoolFalse) == _sqlBoolTrue,
           shouldBackup: (row["should_backup"] ?? _sqlBoolFalse) == _sqlBoolTrue,
-          uploadStrategy: getUploadType(row["upload_strategy"] ?? 0),
+          uploadStrategy: getUploadType((row["upload_strategy"] ?? 0) as int),
         );
         );
         if (includeCoverThumbnail) {
         if (includeCoverThumbnail) {
-          deviceCollection.thumbnail = coverFiles.firstWhere(
+          deviceCollection.thumbnail = coverFiles.firstWhereOrNull(
             (element) => element.localID == deviceCollection.coverId,
             (element) => element.localID == deviceCollection.coverId,
-            orElse: () => null,
           );
           );
           if (deviceCollection.thumbnail == null) {
           if (deviceCollection.thumbnail == null) {
-            final File result =
+            final File? result =
                 await getDeviceCollectionThumbnail(deviceCollection.id);
                 await getDeviceCollectionThumbnail(deviceCollection.id);
             if (result == null) {
             if (result == null) {
               _logger.finest(
               _logger.finest(
@@ -409,7 +411,7 @@ extension DeviceFiles on FilesDB {
       if (includeCoverThumbnail) {
       if (includeCoverThumbnail) {
         deviceCollections.sort(
         deviceCollections.sort(
           (a, b) =>
           (a, b) =>
-              b.thumbnail.creationTime.compareTo(a.thumbnail.creationTime),
+              b.thumbnail!.creationTime!.compareTo(a.thumbnail!.creationTime!),
         );
         );
       }
       }
       return deviceCollections;
       return deviceCollections;
@@ -419,7 +421,7 @@ extension DeviceFiles on FilesDB {
     }
     }
   }
   }
 
 
-  Future<File> getDeviceCollectionThumbnail(String pathID) async {
+  Future<File?> getDeviceCollectionThumbnail(String pathID) async {
     debugPrint("Call fallback method to get potential thumbnail");
     debugPrint("Call fallback method to get potential thumbnail");
     final db = await database;
     final db = await database;
     final fileRows = await db.rawQuery(
     final fileRows = await db.rawQuery(

+ 4 - 5
lib/db/file_updation_db.dart

@@ -128,7 +128,7 @@ class FileUpdationDB {
     );
     );
   }
   }
 
 
-  Future<List<String?>> getLocalIDsForPotentialReUpload(
+  Future<List<String>> getLocalIDsForPotentialReUpload(
     int limit,
     int limit,
     String reason,
     String reason,
   ) async {
   ) async {
@@ -139,15 +139,14 @@ class FileUpdationDB {
       limit: limit,
       limit: limit,
       where: whereClause,
       where: whereClause,
     );
     );
-    final result = <String?>[];
+    final result = <String>[];
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row[columnLocalID] as String?);
+      result.add(row[columnLocalID] as String);
     }
     }
     return result;
     return result;
   }
   }
 
 
-  Map<String, dynamic> _getRowForReUploadTable(String? localID, String reason) {
-    assert(localID != null);
+  Map<String, dynamic> _getRowForReUploadTable(String localID, String reason) {
     final row = <String, dynamic>{};
     final row = <String, dynamic>{};
     row[columnLocalID] = localID;
     row[columnLocalID] = localID;
     row[columnReason] = reason;
     row[columnReason] = reason;

+ 70 - 63
lib/db/files_db.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:io' as io;
 import 'dart:io' as io;
 
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
@@ -94,12 +92,12 @@ class FilesDB {
   static final FilesDB instance = FilesDB._privateConstructor();
   static final FilesDB instance = FilesDB._privateConstructor();
 
 
   // only have a single app-wide reference to the database
   // only have a single app-wide reference to the database
-  static Future<Database> _dbFuture;
+  static Future<Database>? _dbFuture;
 
 
   Future<Database> get database async {
   Future<Database> get database async {
     // lazily instantiate the db the first time it is accessed
     // lazily instantiate the db the first time it is accessed
     _dbFuture ??= _initDatabase();
     _dbFuture ??= _initDatabase();
-    return _dbFuture;
+    return _dbFuture!;
   }
   }
 
 
   // this opens the database (and creates it if it doesn't exist)
   // this opens the database (and creates it if it doesn't exist)
@@ -415,7 +413,7 @@ class FilesDB {
     );
     );
   }
   }
 
 
-  Future<File> getFile(int generatedID) async {
+  Future<File?> getFile(int generatedID) async {
     final db = await instance.database;
     final db = await instance.database;
     final results = await db.query(
     final results = await db.query(
       filesTable,
       filesTable,
@@ -428,7 +426,7 @@ class FilesDB {
     return convertToFiles(results)[0];
     return convertToFiles(results)[0];
   }
   }
 
 
-  Future<File> getUploadedFile(int uploadedID, int collectionID) async {
+  Future<File?> getUploadedFile(int uploadedID, int collectionID) async {
     final db = await instance.database;
     final db = await instance.database;
     final results = await db.query(
     final results = await db.query(
       filesTable,
       filesTable,
@@ -449,14 +447,15 @@ class FilesDB {
     final results = await db.query(
     final results = await db.query(
       filesTable,
       filesTable,
       columns: [columnUploadedFileID],
       columns: [columnUploadedFileID],
-      where: '$columnCollectionID = ?',
+      where:
+          '$columnCollectionID = ? AND ($columnUploadedFileID IS NOT NULL AND $columnUploadedFileID IS NOT -1)',
       whereArgs: [
       whereArgs: [
         collectionID,
         collectionID,
       ],
       ],
     );
     );
     final ids = <int>{};
     final ids = <int>{};
     for (final result in results) {
     for (final result in results) {
-      ids.add(result[columnUploadedFileID]);
+      ids.add(result[columnUploadedFileID] as int);
     }
     }
     return ids;
     return ids;
   }
   }
@@ -472,8 +471,8 @@ class FilesDB {
     final localIDs = <String>{};
     final localIDs = <String>{};
     final uploadedIDs = <int>{};
     final uploadedIDs = <int>{};
     for (final result in results) {
     for (final result in results) {
-      localIDs.add(result[columnLocalID]);
-      uploadedIDs.add(result[columnUploadedFileID]);
+      localIDs.add(result[columnLocalID] as String);
+      uploadedIDs.add(result[columnUploadedFileID] as int);
     }
     }
     return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
     return BackedUpFileIDs(localIDs.toList(), uploadedIDs.toList());
   }
   }
@@ -482,10 +481,10 @@ class FilesDB {
     int startTime,
     int startTime,
     int endTime,
     int endTime,
     int ownerID, {
     int ownerID, {
-    int limit,
-    bool asc,
+    int? limit,
+    bool? asc,
     int visibility = visibilityVisible,
     int visibility = visibilityVisible,
-    Set<int> ignoredCollectionIDs,
+    Set<int>? ignoredCollectionIDs,
   }) async {
   }) async {
     final db = await instance.database;
     final db = await instance.database;
     final order = (asc ?? false ? 'ASC' : 'DESC');
     final order = (asc ?? false ? 'ASC' : 'DESC');
@@ -509,9 +508,9 @@ class FilesDB {
     int startTime,
     int startTime,
     int endTime,
     int endTime,
     int ownerID, {
     int ownerID, {
-    int limit,
-    bool asc,
-    Set<int> ignoredCollectionIDs,
+    int? limit,
+    bool? asc,
+    Set<int>? ignoredCollectionIDs,
   }) async {
   }) async {
     final db = await instance.database;
     final db = await instance.database;
     final order = (asc ?? false ? 'ASC' : 'DESC');
     final order = (asc ?? false ? 'ASC' : 'DESC');
@@ -536,7 +535,10 @@ class FilesDB {
     final List<File> deduplicatedFiles = [];
     final List<File> deduplicatedFiles = [];
     for (final file in files) {
     for (final file in files) {
       final id = file.localID;
       final id = file.localID;
-      if (id != null && localIDs.contains(id)) {
+      if (id == null) {
+        continue;
+      }
+      if (localIDs.contains(id)) {
         continue;
         continue;
       }
       }
       localIDs.add(id);
       localIDs.add(id);
@@ -547,7 +549,7 @@ class FilesDB {
 
 
   List<File> _deduplicatedAndFilterIgnoredFiles(
   List<File> _deduplicatedAndFilterIgnoredFiles(
     List<File> files,
     List<File> files,
-    Set<int> ignoredCollectionIDs,
+    Set<int>? ignoredCollectionIDs,
   ) {
   ) {
     final Set<int> uploadedFileIDs = <int>{};
     final Set<int> uploadedFileIDs = <int>{};
     // ignoredFileUploadIDs is to keep a track of files which are part of
     // ignoredFileUploadIDs is to keep a track of files which are part of
@@ -574,7 +576,9 @@ class FilesDB {
       if (isFileUploaded && uploadedFileIDs.contains(id)) {
       if (isFileUploaded && uploadedFileIDs.contains(id)) {
         continue;
         continue;
       }
       }
-      uploadedFileIDs.add(id);
+      if (isFileUploaded) {
+        uploadedFileIDs.add(id);
+      }
       deduplicatedFiles.add(file);
       deduplicatedFiles.add(file);
     }
     }
     return deduplicatedFiles;
     return deduplicatedFiles;
@@ -584,8 +588,8 @@ class FilesDB {
     int collectionID,
     int collectionID,
     int startTime,
     int startTime,
     int endTime, {
     int endTime, {
-    int limit,
-    bool asc,
+    int? limit,
+    bool? asc,
     int visibility = visibilityVisible,
     int visibility = visibilityVisible,
   }) async {
   }) async {
     final db = await instance.database;
     final db = await instance.database;
@@ -611,8 +615,8 @@ class FilesDB {
     int startTime,
     int startTime,
     int endTime,
     int endTime,
     int userID, {
     int userID, {
-    int limit,
-    bool asc,
+    int? limit,
+    bool? asc,
   }) async {
   }) async {
     if (collectionIDs.isEmpty) {
     if (collectionIDs.isEmpty) {
       return FileLoadResult(<File>[], false);
       return FileLoadResult(<File>[], false);
@@ -725,12 +729,12 @@ class FilesDB {
     );
     );
     final uploadedFileIDs = <int>[];
     final uploadedFileIDs = <int>[];
     for (final row in rows) {
     for (final row in rows) {
-      uploadedFileIDs.add(row[columnUploadedFileID]);
+      uploadedFileIDs.add(row[columnUploadedFileID] as int);
     }
     }
     return uploadedFileIDs;
     return uploadedFileIDs;
   }
   }
 
 
-  Future<File> getUploadedFileInAnyCollection(int uploadedFileID) async {
+  Future<File?> getUploadedFileInAnyCollection(int uploadedFileID) async {
     final db = await instance.database;
     final db = await instance.database;
     final results = await db.query(
     final results = await db.query(
       filesTable,
       filesTable,
@@ -756,7 +760,7 @@ class FilesDB {
     );
     );
     final result = <String>{};
     final result = <String>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row[columnLocalID]);
+      result.add(row[columnLocalID] as String);
     }
     }
     return result;
     return result;
   }
   }
@@ -775,7 +779,7 @@ class FilesDB {
     );
     );
     final result = <String>{};
     final result = <String>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row[columnLocalID]);
+      result.add(row[columnLocalID] as String);
     }
     }
     return result;
     return result;
   }
   }
@@ -790,7 +794,7 @@ class FilesDB {
     );
     );
     final result = <String>{};
     final result = <String>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row[columnLocalID]);
+      result.add(row[columnLocalID] as String);
     }
     }
     return result;
     return result;
   }
   }
@@ -819,19 +823,19 @@ class FilesDB {
 
 
   Future<int> updateUploadedFile(
   Future<int> updateUploadedFile(
     String localID,
     String localID,
-    String title,
-    Location location,
+    String? title,
+    Location? location,
     int creationTime,
     int creationTime,
     int modificationTime,
     int modificationTime,
-    int updationTime,
+    int? updationTime,
   ) async {
   ) async {
     final db = await instance.database;
     final db = await instance.database;
     return await db.update(
     return await db.update(
       filesTable,
       filesTable,
       {
       {
         columnTitle: title,
         columnTitle: title,
-        columnLatitude: location.latitude,
-        columnLongitude: location.longitude,
+        columnLatitude: location?.latitude,
+        columnLongitude: location?.longitude,
         columnCreationTime: creationTime,
         columnCreationTime: creationTime,
         columnModificationTime: modificationTime,
         columnModificationTime: modificationTime,
         columnUpdationTime: updationTime,
         columnUpdationTime: updationTime,
@@ -849,8 +853,8 @@ class FilesDB {
     int ownerID,
     int ownerID,
     String localID,
     String localID,
     FileType fileType, {
     FileType fileType, {
-    @required String title,
-    @required String deviceFolder,
+    required String title,
+    required String deviceFolder,
   }) async {
   }) async {
     final db = await instance.database;
     final db = await instance.database;
     // on iOS, match using localID and fileType. title can either match or
     // on iOS, match using localID and fileType. title can either match or
@@ -1069,7 +1073,7 @@ class FilesDB {
         '$collectionID AND $columnUploadedFileID IS NOT -1',
         '$collectionID AND $columnUploadedFileID IS NOT -1',
       ),
       ),
     );
     );
-    return count;
+    return count ?? 0;
   }
   }
 
 
   Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
   Future<int> fileCountWithVisibility(int visibility, int ownerID) async {
@@ -1081,7 +1085,7 @@ class FilesDB {
         ' = $visibility AND $columnOwnerID = $ownerID',
         ' = $visibility AND $columnOwnerID = $ownerID',
       ),
       ),
     );
     );
-    return count;
+    return count ?? 0;
   }
   }
 
 
   Future<int> deleteCollection(int collectionID) async {
   Future<int> deleteCollection(int collectionID) async {
@@ -1120,7 +1124,9 @@ class FilesDB {
   ) async {
   ) async {
     String inParam = "";
     String inParam = "";
     for (final existingFile in existingFiles) {
     for (final existingFile in existingFiles) {
-      inParam += "'" + existingFile.localID + "',";
+      if (existingFile.localID != null) {
+        inParam += "'" + existingFile.localID! + "',";
+      }
     }
     }
     inParam = inParam.substring(0, inParam.length - 1);
     inParam = inParam.substring(0, inParam.length - 1);
     final db = await instance.database;
     final db = await instance.database;
@@ -1134,7 +1140,7 @@ class FilesDB {
     );
     );
     final result = <String>{};
     final result = <String>{};
     for (final row in rows) {
     for (final row in rows) {
-      result.add(row[columnLocalID]);
+      result.add(row[columnLocalID] as String);
     }
     }
     return result;
     return result;
   }
   }
@@ -1166,11 +1172,12 @@ class FilesDB {
     final collectionMap = <int, File>{};
     final collectionMap = <int, File>{};
     for (final file in files) {
     for (final file in files) {
       if (collectionMap.containsKey(file.collectionID)) {
       if (collectionMap.containsKey(file.collectionID)) {
-        if (collectionMap[file.collectionID].updationTime < file.updationTime) {
+        if (collectionMap[file.collectionID]!.updationTime! <
+            file.updationTime!) {
           continue;
           continue;
         }
         }
       }
       }
-      collectionMap[file.collectionID] = file;
+      collectionMap[file.collectionID!] = file;
     }
     }
     return collectionMap.values.toList();
     return collectionMap.values.toList();
   }
   }
@@ -1226,7 +1233,7 @@ class FilesDB {
     );
     );
     final files = convertToFiles(results);
     final files = convertToFiles(results);
     for (final file in files) {
     for (final file in files) {
-      result[file.uploadedFileID] = file;
+      result[file.uploadedFileID!] = file;
     }
     }
     return result;
     return result;
   }
   }
@@ -1248,7 +1255,7 @@ class FilesDB {
     );
     );
     final files = convertToFiles(results);
     final files = convertToFiles(results);
     for (final file in files) {
     for (final file in files) {
-      result[file.generatedID] = file;
+      result[file.generatedID as int] = file;
     }
     }
     return result;
     return result;
   }
   }
@@ -1273,9 +1280,9 @@ class FilesDB {
     final files = convertToFiles(results);
     final files = convertToFiles(results);
     for (File eachFile in files) {
     for (File eachFile in files) {
       if (!result.containsKey(eachFile.collectionID)) {
       if (!result.containsKey(eachFile.collectionID)) {
-        result[eachFile.collectionID] = <File>[];
+        result[eachFile.collectionID as int] = <File>[];
       }
       }
-      result[eachFile.collectionID].add(eachFile);
+      result[eachFile.collectionID]!.add(eachFile);
     }
     }
     return result;
     return result;
   }
   }
@@ -1293,7 +1300,7 @@ class FilesDB {
     );
     );
     final collectionIDsOfFile = <int>{};
     final collectionIDsOfFile = <int>{};
     for (var result in results) {
     for (var result in results) {
-      collectionIDsOfFile.add(result['collection_id']);
+      collectionIDsOfFile.add(result['collection_id'] as int);
     }
     }
     return collectionIDsOfFile;
     return collectionIDsOfFile;
   }
   }
@@ -1343,7 +1350,9 @@ class FilesDB {
 
 
     final filesCount = <FileType, int>{};
     final filesCount = <FileType, int>{};
     for (var e in result) {
     for (var e in result) {
-      filesCount.addAll({getFileType(e[columnFileType]): e.values.last});
+      filesCount.addAll(
+        {getFileType(e[columnFileType] as int): e.values.last as int},
+      );
     }
     }
     return filesCount;
     return filesCount;
   }
   }
@@ -1360,8 +1369,8 @@ class FilesDB {
     row[columnTitle] = file.title;
     row[columnTitle] = file.title;
     row[columnDeviceFolder] = file.deviceFolder;
     row[columnDeviceFolder] = file.deviceFolder;
     if (file.location != null) {
     if (file.location != null) {
-      row[columnLatitude] = file.location.latitude;
-      row[columnLongitude] = file.location.longitude;
+      row[columnLatitude] = file.location!.latitude;
+      row[columnLongitude] = file.location!.longitude;
     }
     }
     row[columnFileType] = getInt(file.fileType);
     row[columnFileType] = getInt(file.fileType);
     row[columnCreationTime] = file.creationTime;
     row[columnCreationTime] = file.creationTime;
@@ -1378,17 +1387,16 @@ class FilesDB {
     row[columnHash] = file.hash;
     row[columnHash] = file.hash;
     row[columnMetadataVersion] = file.metadataVersion;
     row[columnMetadataVersion] = file.metadataVersion;
     row[columnFileSize] = file.fileSize;
     row[columnFileSize] = file.fileSize;
-    row[columnMMdVersion] = file.mMdVersion ?? 0;
+    row[columnMMdVersion] = file.mMdVersion;
     row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
     row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
-    row[columnMMdVisibility] =
-        file.magicMetadata?.visibility ?? visibilityVisible;
-    row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
+    row[columnMMdVisibility] = file.magicMetadata.visibility;
+    row[columnPubMMdVersion] = file.pubMmdVersion;
     row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
     row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
     if (file.pubMagicMetadata != null &&
     if (file.pubMagicMetadata != null &&
-        file.pubMagicMetadata.editedTime != null) {
+        file.pubMagicMetadata!.editedTime != null) {
       // override existing creationTime to avoid re-writing all queries related
       // override existing creationTime to avoid re-writing all queries related
       // to loading the gallery
       // to loading the gallery
-      row[columnCreationTime] = file.pubMagicMetadata.editedTime;
+      row[columnCreationTime] = file.pubMagicMetadata!.editedTime;
     }
     }
     return row;
     return row;
   }
   }
@@ -1401,8 +1409,8 @@ class FilesDB {
     row[columnTitle] = file.title;
     row[columnTitle] = file.title;
     row[columnDeviceFolder] = file.deviceFolder;
     row[columnDeviceFolder] = file.deviceFolder;
     if (file.location != null) {
     if (file.location != null) {
-      row[columnLatitude] = file.location.latitude;
-      row[columnLongitude] = file.location.longitude;
+      row[columnLatitude] = file.location!.latitude;
+      row[columnLongitude] = file.location!.longitude;
     }
     }
     row[columnFileType] = getInt(file.fileType);
     row[columnFileType] = getInt(file.fileType);
     row[columnCreationTime] = file.creationTime;
     row[columnCreationTime] = file.creationTime;
@@ -1417,18 +1425,17 @@ class FilesDB {
     row[columnHash] = file.hash;
     row[columnHash] = file.hash;
     row[columnMetadataVersion] = file.metadataVersion;
     row[columnMetadataVersion] = file.metadataVersion;
 
 
-    row[columnMMdVersion] = file.mMdVersion ?? 0;
+    row[columnMMdVersion] = file.mMdVersion;
     row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
     row[columnMMdEncodedJson] = file.mMdEncodedJson ?? '{}';
-    row[columnMMdVisibility] =
-        file.magicMetadata?.visibility ?? visibilityVisible;
+    row[columnMMdVisibility] = file.magicMetadata.visibility;
 
 
-    row[columnPubMMdVersion] = file.pubMmdVersion ?? 0;
+    row[columnPubMMdVersion] = file.pubMmdVersion;
     row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
     row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
     if (file.pubMagicMetadata != null &&
     if (file.pubMagicMetadata != null &&
-        file.pubMagicMetadata.editedTime != null) {
+        file.pubMagicMetadata!.editedTime != null) {
       // override existing creationTime to avoid re-writing all queries related
       // override existing creationTime to avoid re-writing all queries related
       // to loading the gallery
       // to loading the gallery
-      row[columnCreationTime] = file.pubMagicMetadata.editedTime;
+      row[columnCreationTime] = file.pubMagicMetadata!.editedTime!;
     }
     }
     return row;
     return row;
   }
   }

+ 2 - 4
lib/db/ignored_files_db.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:io';
 import 'dart:io';
 
 
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -44,12 +42,12 @@ class IgnoredFilesDB {
   static final IgnoredFilesDB instance = IgnoredFilesDB._privateConstructor();
   static final IgnoredFilesDB instance = IgnoredFilesDB._privateConstructor();
 
 
   // only have a single app-wide reference to the database
   // only have a single app-wide reference to the database
-  static Future<Database> _dbFuture;
+  static Future<Database>? _dbFuture;
 
 
   Future<Database> get database async {
   Future<Database> get database async {
     // lazily instantiate the db the first time it is accessed
     // lazily instantiate the db the first time it is accessed
     _dbFuture ??= _initDatabase();
     _dbFuture ??= _initDatabase();
-    return _dbFuture;
+    return _dbFuture!;
   }
   }
 
 
   // this opens the database (and creates it if it doesn't exist)
   // this opens the database (and creates it if it doesn't exist)

+ 4 - 5
lib/db/memories_db.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
@@ -20,14 +18,15 @@ class MemoriesDB {
   MemoriesDB._privateConstructor();
   MemoriesDB._privateConstructor();
   static final MemoriesDB instance = MemoriesDB._privateConstructor();
   static final MemoriesDB instance = MemoriesDB._privateConstructor();
 
 
-  static Future<Database> _dbFuture;
+  static Future<Database>? _dbFuture;
   Future<Database> get database async {
   Future<Database> get database async {
     _dbFuture ??= _initDatabase();
     _dbFuture ??= _initDatabase();
-    return _dbFuture;
+    return _dbFuture!;
   }
   }
 
 
   Future<Database> _initDatabase() async {
   Future<Database> _initDatabase() async {
-    final Directory documentsDirectory = await getApplicationDocumentsDirectory();
+    final Directory documentsDirectory =
+        await getApplicationDocumentsDirectory();
     final String path = join(documentsDirectory.path, _databaseName);
     final String path = join(documentsDirectory.path, _databaseName);
     return await openDatabase(
     return await openDatabase(
       path,
       path,

+ 12 - 14
lib/db/trash_db.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
 import 'dart:io';
 import 'dart:io';
 
 
@@ -77,12 +75,12 @@ class TrashDB {
   static final TrashDB instance = TrashDB._privateConstructor();
   static final TrashDB instance = TrashDB._privateConstructor();
 
 
   // only have a single app-wide reference to the database
   // only have a single app-wide reference to the database
-  static Future<Database> _dbFuture;
+  static Future<Database>? _dbFuture;
 
 
   Future<Database> get database async {
   Future<Database> get database async {
     // lazily instantiate the db the first time it is accessed
     // lazily instantiate the db the first time it is accessed
     _dbFuture ??= _initDatabase();
     _dbFuture ??= _initDatabase();
-    return _dbFuture;
+    return _dbFuture!;
   }
   }
 
 
   // this opens the database (and creates it if it doesn't exist)
   // this opens the database (and creates it if it doesn't exist)
@@ -104,14 +102,14 @@ class TrashDB {
   }
   }
 
 
   // getRecentlyTrashedFile returns the file which was trashed recently
   // getRecentlyTrashedFile returns the file which was trashed recently
-  Future<TrashFile> getRecentlyTrashedFile() async {
+  Future<TrashFile?> getRecentlyTrashedFile() async {
     final db = await instance.database;
     final db = await instance.database;
     final rows = await db.query(
     final rows = await db.query(
       tableName,
       tableName,
       orderBy: '$columnTrashDeleteBy DESC',
       orderBy: '$columnTrashDeleteBy DESC',
       limit: 1,
       limit: 1,
     );
     );
-    if (rows == null || rows.isEmpty) {
+    if (rows.isEmpty) {
       return null;
       return null;
     }
     }
     return _getTrashFromRow(rows[0]);
     return _getTrashFromRow(rows[0]);
@@ -122,7 +120,7 @@ class TrashDB {
     final count = Sqflite.firstIntValue(
     final count = Sqflite.firstIntValue(
       await db.rawQuery('SELECT COUNT(*) FROM $tableName'),
       await db.rawQuery('SELECT COUNT(*) FROM $tableName'),
     );
     );
-    return count;
+    return count ?? 0;
   }
   }
 
 
   Future<void> insertMultiple(List<TrashFile> trashFiles) async {
   Future<void> insertMultiple(List<TrashFile> trashFiles) async {
@@ -188,8 +186,8 @@ class TrashDB {
   Future<FileLoadResult> getTrashedFiles(
   Future<FileLoadResult> getTrashedFiles(
     int startTime,
     int startTime,
     int endTime, {
     int endTime, {
-    int limit,
-    bool asc,
+    int? limit,
+    bool? asc,
   }) async {
   }) async {
     final db = await instance.database;
     final db = await instance.database;
     final order = (asc ?? false ? 'ASC' : 'DESC');
     final order = (asc ?? false ? 'ASC' : 'DESC');
@@ -218,7 +216,7 @@ class TrashDB {
     trashFile.deleteBy = row[columnTrashDeleteBy];
     trashFile.deleteBy = row[columnTrashDeleteBy];
     trashFile.uploadedFileID = row[columnUploadedFileID];
     trashFile.uploadedFileID = row[columnUploadedFileID];
     // dirty hack to ensure that the file_downloads & cache mechanism works
     // dirty hack to ensure that the file_downloads & cache mechanism works
-    trashFile.generatedID = -1 * trashFile.uploadedFileID;
+    trashFile.generatedID = -1 * trashFile.uploadedFileID!;
     trashFile.ownerID = row[columnOwnerID];
     trashFile.ownerID = row[columnOwnerID];
     trashFile.collectionID =
     trashFile.collectionID =
         row[columnCollectionID] == -1 ? null : row[columnCollectionID];
         row[columnCollectionID] == -1 ? null : row[columnCollectionID];
@@ -240,10 +238,10 @@ class TrashDB {
     trashFile.pubMmdEncodedJson = row[columnPubMMdEncodedJson] ?? '{}';
     trashFile.pubMmdEncodedJson = row[columnPubMMdEncodedJson] ?? '{}';
 
 
     if (trashFile.pubMagicMetadata != null &&
     if (trashFile.pubMagicMetadata != null &&
-        trashFile.pubMagicMetadata.editedTime != null) {
+        trashFile.pubMagicMetadata!.editedTime != null) {
       // override existing creationTime to avoid re-writing all queries related
       // override existing creationTime to avoid re-writing all queries related
       // to loading the gallery
       // to loading the gallery
-      row[columnCreationTime] = trashFile.pubMagicMetadata.editedTime;
+      row[columnCreationTime] = trashFile.pubMagicMetadata!.editedTime!;
     }
     }
 
 
     return trashFile;
     return trashFile;
@@ -266,10 +264,10 @@ class TrashDB {
     row[columnCreationTime] = trash.creationTime;
     row[columnCreationTime] = trash.creationTime;
     row[columnFileMetadata] = jsonEncode(trash.metadata);
     row[columnFileMetadata] = jsonEncode(trash.metadata);
 
 
-    row[columnMMdVersion] = trash.mMdVersion ?? 0;
+    row[columnMMdVersion] = trash.mMdVersion;
     row[columnMMdEncodedJson] = trash.mMdEncodedJson ?? '{}';
     row[columnMMdEncodedJson] = trash.mMdEncodedJson ?? '{}';
 
 
-    row[columnPubMMdVersion] = trash.pubMmdVersion ?? 0;
+    row[columnPubMMdVersion] = trash.pubMmdVersion;
     row[columnPubMMdEncodedJson] = trash.pubMmdEncodedJson ?? '{}';
     row[columnPubMMdEncodedJson] = trash.pubMmdEncodedJson ?? '{}';
     return row;
     return row;
   }
   }

+ 1 - 3
lib/events/collection_updated_event.dart

@@ -1,9 +1,7 @@
-// @dart=2.9
-
 import 'package:photos/events/files_updated_event.dart';
 import 'package:photos/events/files_updated_event.dart';
 
 
 class CollectionUpdatedEvent extends FilesUpdatedEvent {
 class CollectionUpdatedEvent extends FilesUpdatedEvent {
-  final int collectionID;
+  final int? collectionID;
 
 
   CollectionUpdatedEvent(this.collectionID, updatedFiles, source, {type})
   CollectionUpdatedEvent(this.collectionID, updatedFiles, source, {type})
       : super(
       : super(

+ 0 - 2
lib/events/files_updated_event.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:photos/events/event.dart';
 import 'package:photos/events/event.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';
 
 

+ 3 - 3
lib/main.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
@@ -230,7 +230,7 @@ Future<bool> _isRunningInForeground() async {
       (currentTime - kFGTaskDeathTimeoutInMicroseconds);
       (currentTime - kFGTaskDeathTimeoutInMicroseconds);
 }
 }
 
 
-Future<void> _killBGTask([String taskId]) async {
+Future<void> _killBGTask([String? taskId]) async {
   await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore(
   await UploadLocksDB.instance.releaseLocksAcquiredByOwnerBefore(
     ProcessType.background.toString(),
     ProcessType.background.toString(),
     DateTime.now().microsecondsSinceEpoch,
     DateTime.now().microsecondsSinceEpoch,
@@ -281,7 +281,7 @@ Future<void> _logFGHeartBeatInfo() async {
   _logger.info('isAlreaduunningFG: $isRunningInFG, last Beat: $lastRun');
   _logger.info('isAlreaduunningFG: $isRunningInFG, last Beat: $lastRun');
 }
 }
 
 
-void _scheduleSuicide(Duration duration, [String taskID]) {
+void _scheduleSuicide(Duration duration, [String? taskID]) {
   final taskIDVal = taskID ?? 'no taskID';
   final taskIDVal = taskID ?? 'no taskID';
   _logger.warning("Schedule seppuku taskID: $taskIDVal");
   _logger.warning("Schedule seppuku taskID: $taskIDVal");
   Future.delayed(duration, () {
   Future.delayed(duration, () {

+ 4 - 2
lib/models/collection.dart

@@ -10,8 +10,10 @@ class Collection {
   final String encryptedKey;
   final String encryptedKey;
   final String? keyDecryptionNonce;
   final String? keyDecryptionNonce;
   final String? name;
   final String? name;
-  final String encryptedName;
-  final String nameDecryptionNonce;
+  // encryptedName & nameDecryptionNonce will be null for collections
+  // created before we started encrypting collection name
+  final String? encryptedName;
+  final String? nameDecryptionNonce;
   final CollectionType type;
   final CollectionType type;
   final CollectionAttributes attributes;
   final CollectionAttributes attributes;
   final List<User?>? sharees;
   final List<User?>? sharees;

+ 3 - 4
lib/models/duplicate_files.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';
@@ -58,9 +56,10 @@ class DuplicateFiles {
   sortByCollectionName() {
   sortByCollectionName() {
     files.sort((first, second) {
     files.sort((first, second) {
       final firstName =
       final firstName =
-          collectionsService.getCollectionByID(first.collectionID).name;
+          collectionsService.getCollectionByID(first.collectionID!)!.name ?? '';
       final secondName =
       final secondName =
-          collectionsService.getCollectionByID(second.collectionID).name;
+          collectionsService.getCollectionByID(second.collectionID!)!.name ??
+              '';
       return firstName.compareTo(secondName);
       return firstName.compareTo(secondName);
     });
     });
   }
   }

+ 1 - 4
lib/models/file.dart

@@ -10,12 +10,9 @@ import 'package:photos/models/ente_file.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/file_type.dart';
 import 'package:photos/models/location.dart';
 import 'package:photos/models/location.dart';
 import 'package:photos/models/magic_metadata.dart';
 import 'package:photos/models/magic_metadata.dart';
-// ignore: import_of_legacy_library_into_null_safe
 import 'package:photos/services/feature_flag_service.dart';
 import 'package:photos/services/feature_flag_service.dart';
 import 'package:photos/utils/date_time_util.dart';
 import 'package:photos/utils/date_time_util.dart';
-// ignore: import_of_legacy_library_into_null_safe
 import 'package:photos/utils/exif_util.dart';
 import 'package:photos/utils/exif_util.dart';
-// ignore: import_of_legacy_library_into_null_safe
 import 'package:photos/utils/file_uploader_util.dart';
 import 'package:photos/utils/file_uploader_util.dart';
 
 
 class File extends EnteFile {
 class File extends EnteFile {
@@ -152,7 +149,7 @@ class File extends EnteFile {
     } else {
     } else {
       location = Location(latitude, longitude);
       location = Location(latitude, longitude);
     }
     }
-    fileType = getFileType(metadata["fileType"]);
+    fileType = getFileType(metadata["fileType"] ?? -1);
     fileSubType = metadata["subType"] ?? -1;
     fileSubType = metadata["subType"] ?? -1;
     duration = metadata["duration"] ?? 0;
     duration = metadata["duration"] ?? 0;
     exif = metadata["exif"];
     exif = metadata["exif"];

+ 2 - 2
lib/models/key_attributes.dart

@@ -11,8 +11,8 @@ class KeyAttributes {
   final int opsLimit;
   final int opsLimit;
   final String masterKeyEncryptedWithRecoveryKey;
   final String masterKeyEncryptedWithRecoveryKey;
   final String masterKeyDecryptionNonce;
   final String masterKeyDecryptionNonce;
-  final String recoveryKeyEncryptedWithMasterKey;
-  final String recoveryKeyDecryptionNonce;
+  final String? recoveryKeyEncryptedWithMasterKey;
+  final String? recoveryKeyDecryptionNonce;
 
 
   KeyAttributes(
   KeyAttributes(
     this.kekSalt,
     this.kekSalt,

+ 1 - 3
lib/services/billing_service.dart

@@ -53,9 +53,7 @@ class BillingService {
             purchase.productID,
             purchase.productID,
             purchase.verificationData.serverVerificationData,
             purchase.verificationData.serverVerificationData,
           ).then((response) {
           ).then((response) {
-            if (response != null) {
-              InAppPurchase.instance.completePurchase(purchase);
-            }
+            InAppPurchase.instance.completePurchase(purchase);
           });
           });
         } else if (Platform.isIOS && purchase.pendingCompletePurchase) {
         } else if (Platform.isIOS && purchase.pendingCompletePurchase) {
           InAppPurchase.instance.completePurchase(purchase);
           InAppPurchase.instance.completePurchase(purchase);

+ 158 - 156
lib/services/collections_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:convert';
 import 'dart:convert';
 import 'dart:math';
 import 'dart:math';
@@ -47,17 +45,18 @@ class CollectionsService {
 
 
   final _logger = Logger("CollectionsService");
   final _logger = Logger("CollectionsService");
 
 
-  CollectionsDB _db;
-  FilesDB _filesDB;
-  Configuration _config;
-  SharedPreferences _prefs;
-  Future<List<File>> _cachedLatestFiles;
+  late CollectionsDB _db;
+  late FilesDB _filesDB;
+  late Configuration _config;
+  late SharedPreferences _prefs;
+
   final _enteDio = Network.instance.enteDio;
   final _enteDio = Network.instance.enteDio;
   final _localPathToCollectionID = <String, int>{};
   final _localPathToCollectionID = <String, int>{};
   final _collectionIDToCollections = <int, Collection>{};
   final _collectionIDToCollections = <int, Collection>{};
   final _cachedKeys = <int, Uint8List>{};
   final _cachedKeys = <int, Uint8List>{};
   final _cachedUserIdToUser = <int, User>{};
   final _cachedUserIdToUser = <int, User>{};
-  Collection cachedDefaultHiddenCollection;
+  Collection? cachedDefaultHiddenCollection;
+  Future<List<File>>? _cachedLatestFiles;
 
 
   CollectionsService._privateConstructor() {
   CollectionsService._privateConstructor() {
     _db = CollectionsDB.instance;
     _db = CollectionsDB.instance;
@@ -117,7 +116,7 @@ class CollectionsService {
         }
         }
       }
       }
       // remove reference for incoming collections when unshared/deleted
       // remove reference for incoming collections when unshared/deleted
-      if (collection.isDeleted && ownerID != collection?.owner?.id) {
+      if (collection.isDeleted && ownerID != collection.owner?.id) {
         await _db.deleteCollection(collection.id);
         await _db.deleteCollection(collection.id);
       } else {
       } else {
         // keep entry for deletedCollection as collectionKey may be used during
         // keep entry for deletedCollection as collectionKey may be used during
@@ -205,10 +204,10 @@ class CollectionsService {
 
 
   Future<List<File>> getLatestCollectionFiles() {
   Future<List<File>> getLatestCollectionFiles() {
     _cachedLatestFiles ??= _filesDB.getLatestCollectionFiles();
     _cachedLatestFiles ??= _filesDB.getLatestCollectionFiles();
-    return _cachedLatestFiles;
+    return _cachedLatestFiles!;
   }
   }
 
 
-  Future<void> setCollectionSyncTime(int collectionID, int time) async {
+  Future<bool> setCollectionSyncTime(int collectionID, int? time) async {
     final key = _collectionSyncTimeKeyPrefix + collectionID.toString();
     final key = _collectionSyncTimeKeyPrefix + collectionID.toString();
     if (time == null) {
     if (time == null) {
       return _prefs.remove(key);
       return _prefs.remove(key);
@@ -224,21 +223,21 @@ class CollectionsService {
         .toList();
         .toList();
   }
   }
 
 
-  User getFileOwner(int userID, int collectionID) {
+  User getFileOwner(int userID, int? collectionID) {
     if (_cachedUserIdToUser.containsKey(userID)) {
     if (_cachedUserIdToUser.containsKey(userID)) {
-      return _cachedUserIdToUser[userID];
+      return _cachedUserIdToUser[userID]!;
     }
     }
     if (collectionID != null) {
     if (collectionID != null) {
-      final Collection collection = getCollectionByID(collectionID);
+      final Collection? collection = getCollectionByID(collectionID);
       if (collection != null) {
       if (collection != null) {
-        if (collection.owner.id == userID) {
-          _cachedUserIdToUser[userID] = collection.owner;
+        if (collection.owner?.id == userID) {
+          _cachedUserIdToUser[userID] = collection.owner!;
         } else {
         } else {
           final matchingUser = collection.getSharees().firstWhereOrNull(
           final matchingUser = collection.getSharees().firstWhereOrNull(
                 (u) => u.id == userID,
                 (u) => u.id == userID,
               );
               );
           if (matchingUser != null) {
           if (matchingUser != null) {
-            _cachedUserIdToUser[userID] = collection.owner;
+            _cachedUserIdToUser[userID] = collection.owner!;
           }
           }
         }
         }
       }
       }
@@ -265,20 +264,20 @@ class CollectionsService {
       if (includeCollabCollections) {
       if (includeCollabCollections) {
         usersCollection.removeWhere(
         usersCollection.removeWhere(
           (c) =>
           (c) =>
-              (c.owner.id != userID) &&
+              (c.owner?.id != userID) &&
               (c.getSharees().any((u) => (u.id ?? -1) == userID && u.isViewer)),
               (c.getSharees().any((u) => (u.id ?? -1) == userID && u.isViewer)),
         );
         );
       } else {
       } else {
-        usersCollection.removeWhere((c) => c.owner.id != userID);
+        usersCollection.removeWhere((c) => c.owner?.id != userID);
       }
       }
     }
     }
     final latestCollectionFiles = await getLatestCollectionFiles();
     final latestCollectionFiles = await getLatestCollectionFiles();
     final Map<int, File> collectionToThumbnailMap = Map.fromEntries(
     final Map<int, File> collectionToThumbnailMap = Map.fromEntries(
-      latestCollectionFiles.map((e) => MapEntry(e.collectionID, e)),
+      latestCollectionFiles.map((e) => MapEntry(e.collectionID!, e)),
     );
     );
 
 
     for (final c in usersCollection) {
     for (final c in usersCollection) {
-      final File thumbnail = collectionToThumbnailMap[c.id];
+      final File? thumbnail = collectionToThumbnailMap[c.id];
       collectionsWithThumbnail.add(CollectionWithThumbnail(c, thumbnail));
       collectionsWithThumbnail.add(CollectionWithThumbnail(c, thumbnail));
     }
     }
     return collectionsWithThumbnail;
     return collectionsWithThumbnail;
@@ -325,12 +324,12 @@ class CollectionsService {
         sharees.add(User.fromMap(user));
         sharees.add(User.fromMap(user));
       }
       }
       _collectionIDToCollections[collectionID] =
       _collectionIDToCollections[collectionID] =
-          _collectionIDToCollections[collectionID].copyWith(sharees: sharees);
-      unawaited(_db.insert([_collectionIDToCollections[collectionID]]));
+          _collectionIDToCollections[collectionID]!.copyWith(sharees: sharees);
+      unawaited(_db.insert([_collectionIDToCollections[collectionID]!]));
       RemoteSyncService.instance.sync(silently: true).ignore();
       RemoteSyncService.instance.sync(silently: true).ignore();
       return sharees;
       return sharees;
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response.statusCode == 402) {
+      if (e.response?.statusCode == 402) {
         throw SharingNotPermittedForFreeAccountsError();
         throw SharingNotPermittedForFreeAccountsError();
       }
       }
       rethrow;
       rethrow;
@@ -351,15 +350,14 @@ class CollectionsService {
         sharees.add(User.fromMap(user));
         sharees.add(User.fromMap(user));
       }
       }
       _collectionIDToCollections[collectionID] =
       _collectionIDToCollections[collectionID] =
-          _collectionIDToCollections[collectionID].copyWith(sharees: sharees);
-      unawaited(_db.insert([_collectionIDToCollections[collectionID]]));
+          _collectionIDToCollections[collectionID]!.copyWith(sharees: sharees);
+      unawaited(_db.insert([_collectionIDToCollections[collectionID]!]));
       RemoteSyncService.instance.sync(silently: true).ignore();
       RemoteSyncService.instance.sync(silently: true).ignore();
       return sharees;
       return sharees;
     } catch (e) {
     } catch (e) {
       _logger.severe(e);
       _logger.severe(e);
       rethrow;
       rethrow;
     }
     }
-    RemoteSyncService.instance.sync(silently: true).ignore();
   }
   }
 
 
   Future<void> trashCollection(
   Future<void> trashCollection(
@@ -397,6 +395,29 @@ class CollectionsService {
     }
     }
   }
   }
 
 
+  Future<void> trashEmptyCollection(Collection collection) async {
+    try {
+      // While trashing empty albums, we must pass keepFiles flag as True.
+      // The server will verify that the collection is actually empty before
+      // deleting the files. If keepFiles is set as False and the collection
+      // is not empty, then the files in the collections will be moved to trash.
+      await _enteDio.delete(
+        "/collections/v3/${collection.id}?keepFiles=True&collectionID=${collection.id}",
+      );
+      final deletedCollection = collection.copyWith(isDeleted: true);
+      _collectionIDToCollections[collection.id] = deletedCollection;
+      unawaited(_db.insert([deletedCollection]));
+    } on DioError catch (e) {
+      if (e.response != null) {
+        debugPrint("Error " + e.response!.toString());
+      }
+      rethrow;
+    } catch (e) {
+      _logger.severe('failed to trash empty collection', e);
+      rethrow;
+    }
+  }
+
   Future<void> _handleCollectionDeletion(Collection collection) async {
   Future<void> _handleCollectionDeletion(Collection collection) async {
     await _filesDB.deleteCollection(collection.id);
     await _filesDB.deleteCollection(collection.id);
     final deletedCollection = collection.copyWith(isDeleted: true);
     final deletedCollection = collection.copyWith(isDeleted: true);
@@ -427,7 +448,7 @@ class CollectionsService {
       _cachedKeys[collectionID] =
       _cachedKeys[collectionID] =
           _getAndCacheDecryptedKey(collection, source: "getCollectionKey");
           _getAndCacheDecryptedKey(collection, source: "getCollectionKey");
     }
     }
-    return _cachedKeys[collectionID];
+    return _cachedKeys[collectionID]!;
   }
   }
 
 
   Uint8List _getAndCacheDecryptedKey(
   Uint8List _getAndCacheDecryptedKey(
@@ -435,33 +456,31 @@ class CollectionsService {
     String source = "",
     String source = "",
   }) {
   }) {
     if (_cachedKeys.containsKey(collection.id)) {
     if (_cachedKeys.containsKey(collection.id)) {
-      return _cachedKeys[collection.id];
+      return _cachedKeys[collection.id]!;
     }
     }
     debugPrint(
     debugPrint(
       "Compute collection decryption key for ${collection.id} source"
       "Compute collection decryption key for ${collection.id} source"
       " $source",
       " $source",
     );
     );
     final encryptedKey = Sodium.base642bin(collection.encryptedKey);
     final encryptedKey = Sodium.base642bin(collection.encryptedKey);
-    Uint8List collectionKey;
-    if (collection.owner.id == _config.getUserID()) {
+    Uint8List? collectionKey;
+    if (collection.owner?.id == _config.getUserID()) {
       if (_config.getKey() == null) {
       if (_config.getKey() == null) {
         throw Exception("key can not be null");
         throw Exception("key can not be null");
       }
       }
       collectionKey = CryptoUtil.decryptSync(
       collectionKey = CryptoUtil.decryptSync(
         encryptedKey,
         encryptedKey,
         _config.getKey(),
         _config.getKey(),
-        Sodium.base642bin(collection.keyDecryptionNonce),
+        Sodium.base642bin(collection.keyDecryptionNonce!),
       );
       );
     } else {
     } else {
       collectionKey = CryptoUtil.openSealSync(
       collectionKey = CryptoUtil.openSealSync(
         encryptedKey,
         encryptedKey,
-        Sodium.base642bin(_config.getKeyAttributes().publicKey),
-        _config.getSecretKey(),
+        Sodium.base642bin(_config.getKeyAttributes()!.publicKey),
+        _config.getSecretKey()!,
       );
       );
     }
     }
-    if (collectionKey != null) {
-      _cachedKeys[collection.id] = collectionKey;
-    }
+    _cachedKeys[collection.id] = collectionKey;
     return collectionKey;
     return collectionKey;
   }
   }
 
 
@@ -473,15 +492,15 @@ class CollectionsService {
         await updateMagicMetadata(collection, {"subType": 0});
         await updateMagicMetadata(collection, {"subType": 0});
       }
       }
       final encryptedName = CryptoUtil.encryptSync(
       final encryptedName = CryptoUtil.encryptSync(
-        utf8.encode(newName),
+        utf8.encode(newName) as Uint8List,
         getCollectionKey(collection.id),
         getCollectionKey(collection.id),
       );
       );
       await _enteDio.post(
       await _enteDio.post(
         "/collections/rename",
         "/collections/rename",
         data: {
         data: {
           "collectionID": collection.id,
           "collectionID": collection.id,
-          "encryptedName": Sodium.bin2base64(encryptedName.encryptedData),
-          "nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce)
+          "encryptedName": Sodium.bin2base64(encryptedName.encryptedData!),
+          "nameDecryptionNonce": Sodium.bin2base64(encryptedName.nonce!)
         },
         },
       );
       );
       // trigger sync to fetch the latest name from server
       // trigger sync to fetch the latest name from server
@@ -508,9 +527,9 @@ class CollectionsService {
     Collection collection,
     Collection collection,
     Map<String, dynamic> newMetadataUpdate,
     Map<String, dynamic> newMetadataUpdate,
   ) async {
   ) async {
-    final int ownerID = Configuration.instance.getUserID();
+    final int ownerID = Configuration.instance.getUserID()!;
     try {
     try {
-      if (collection.owner.id != ownerID) {
+      if (collection.owner?.id != ownerID) {
         throw AssertionError("cannot modify albums not owned by you");
         throw AssertionError("cannot modify albums not owned by you");
       }
       }
       // read the existing magic metadata and apply new updates to existing data
       // read the existing magic metadata and apply new updates to existing data
@@ -528,7 +547,7 @@ class CollectionsService {
 
 
       final key = getCollectionKey(collection.id);
       final key = getCollectionKey(collection.id);
       final encryptedMMd = await CryptoUtil.encryptChaCha(
       final encryptedMMd = await CryptoUtil.encryptChaCha(
-        utf8.encode(jsonEncode(jsonToUpdate)),
+        utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
         key,
         key,
       );
       );
       // for required field, the json validator on golang doesn't treat 0 as valid
       // for required field, the json validator on golang doesn't treat 0 as valid
@@ -539,8 +558,8 @@ class CollectionsService {
         magicMetadata: MetadataRequest(
         magicMetadata: MetadataRequest(
           version: currentVersion,
           version: currentVersion,
           count: jsonToUpdate.length,
           count: jsonToUpdate.length,
-          data: Sodium.bin2base64(encryptedMMd.encryptedData),
-          header: Sodium.bin2base64(encryptedMMd.header),
+          data: Sodium.bin2base64(encryptedMMd.encryptedData!),
+          header: Sodium.bin2base64(encryptedMMd.header!),
         ),
         ),
       );
       );
       await _enteDio.put(
       await _enteDio.put(
@@ -552,7 +571,7 @@ class CollectionsService {
       // trigger sync to fetch the latest collection state from server
       // trigger sync to fetch the latest collection state from server
       sync().ignore();
       sync().ignore();
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response != null && e.response.statusCode == 409) {
+      if (e.response != null && e.response?.statusCode == 409) {
         _logger.severe('collection magic data out of sync');
         _logger.severe('collection magic data out of sync');
         sync().ignore();
         sync().ignore();
       }
       }
@@ -578,7 +597,7 @@ class CollectionsService {
         CollectionUpdatedEvent(collection.id, <File>[], "shareUrL"),
         CollectionUpdatedEvent(collection.id, <File>[], "shareUrL"),
       );
       );
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response.statusCode == 402) {
+      if (e.response?.statusCode == 402) {
         throw SharingNotPermittedForFreeAccountsError();
         throw SharingNotPermittedForFreeAccountsError();
       }
       }
       rethrow;
       rethrow;
@@ -606,7 +625,7 @@ class CollectionsService {
       Bus.instance
       Bus.instance
           .fire(CollectionUpdatedEvent(collection.id, <File>[], "updateUrl"));
           .fire(CollectionUpdatedEvent(collection.id, <File>[], "updateUrl"));
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response.statusCode == 402) {
+      if (e.response?.statusCode == 402) {
         throw SharingNotPermittedForFreeAccountsError();
         throw SharingNotPermittedForFreeAccountsError();
       }
       }
       rethrow;
       rethrow;
@@ -621,7 +640,7 @@ class CollectionsService {
       await _enteDio.delete(
       await _enteDio.delete(
         "/collections/share-url/" + collection.id.toString(),
         "/collections/share-url/" + collection.id.toString(),
       );
       );
-      collection.publicURLs.clear();
+      collection.publicURLs?.clear();
       await _db.insert(List.from([collection]));
       await _db.insert(List.from([collection]));
       _cacheCollectionAttributes(collection);
       _cacheCollectionAttributes(collection);
       Bus.instance.fire(
       Bus.instance.fire(
@@ -647,26 +666,24 @@ class CollectionsService {
         },
         },
       );
       );
       final List<Collection> collections = [];
       final List<Collection> collections = [];
-      if (response != null) {
-        final c = response.data["collections"];
-        for (final collectionData in c) {
-          final collection = Collection.fromMap(collectionData);
-          if (collectionData['magicMetadata'] != null) {
-            final decryptionKey =
-                _getAndCacheDecryptedKey(collection, source: "fetchCollection");
-            final utfEncodedMmd = await CryptoUtil.decryptChaCha(
-              Sodium.base642bin(collectionData['magicMetadata']['data']),
-              decryptionKey,
-              Sodium.base642bin(collectionData['magicMetadata']['header']),
-            );
-            collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
-            collection.mMdVersion = collectionData['magicMetadata']['version'];
-            collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
-              collection.mMdEncodedJson,
-            );
-          }
-          collections.add(collection);
+      final c = response.data["collections"];
+      for (final collectionData in c) {
+        final collection = Collection.fromMap(collectionData);
+        if (collectionData['magicMetadata'] != null) {
+          final decryptionKey =
+              _getAndCacheDecryptedKey(collection, source: "fetchCollection");
+          final utfEncodedMmd = await CryptoUtil.decryptChaCha(
+            Sodium.base642bin(collectionData['magicMetadata']['data']),
+            decryptionKey,
+            Sodium.base642bin(collectionData['magicMetadata']['header']),
+          );
+          collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
+          collection.mMdVersion = collectionData['magicMetadata']['version'];
+          collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
+            collection.mMdEncodedJson,
+          );
         }
         }
+        collections.add(collection);
       }
       }
       return collections;
       return collections;
     } catch (e) {
     } catch (e) {
@@ -677,28 +694,23 @@ class CollectionsService {
     }
     }
   }
   }
 
 
-  Collection getCollectionByID(int collectionID) {
+  Collection? getCollectionByID(int collectionID) {
     return _collectionIDToCollections[collectionID];
     return _collectionIDToCollections[collectionID];
   }
   }
 
 
   Future<Collection> createAlbum(String albumName) async {
   Future<Collection> createAlbum(String albumName) async {
     final key = CryptoUtil.generateKey();
     final key = CryptoUtil.generateKey();
-    final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
-    final encryptedName = CryptoUtil.encryptSync(utf8.encode(albumName), key);
+    final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
+    final encryptedName =
+        CryptoUtil.encryptSync(utf8.encode(albumName) as Uint8List, key);
     final collection = await createAndCacheCollection(
     final collection = await createAndCacheCollection(
-      Collection(
-        null,
-        null,
-        Sodium.bin2base64(encryptedKeyData.encryptedData),
-        Sodium.bin2base64(encryptedKeyData.nonce),
-        null,
-        Sodium.bin2base64(encryptedName.encryptedData),
-        Sodium.bin2base64(encryptedName.nonce),
-        CollectionType.album,
-        CollectionAttributes(),
-        null,
-        null,
-        null,
+      CreateRequest(
+        encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
+        keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
+        encryptedName: Sodium.bin2base64(encryptedName.encryptedData!),
+        nameDecryptionNonce: Sodium.bin2base64(encryptedName.nonce!),
+        type: CollectionType.album,
+        attributes: CollectionAttributes(),
       ),
       ),
     );
     );
     return collection;
     return collection;
@@ -710,7 +722,7 @@ class CollectionsService {
       final response = await _enteDio.get(
       final response = await _enteDio.get(
         "/collections/$collectionID",
         "/collections/$collectionID",
       );
       );
-      assert(response != null && response.data != null);
+      assert(response.data != null);
       final collectionData = response.data["collection"];
       final collectionData = response.data["collection"];
       final collection = Collection.fromMap(collectionData);
       final collection = Collection.fromMap(collectionData);
       if (collectionData['magicMetadata'] != null) {
       if (collectionData['magicMetadata'] != null) {
@@ -742,44 +754,38 @@ class CollectionsService {
 
 
   Future<Collection> getOrCreateForPath(String path) async {
   Future<Collection> getOrCreateForPath(String path) async {
     if (_localPathToCollectionID.containsKey(path)) {
     if (_localPathToCollectionID.containsKey(path)) {
-      final Collection cachedCollection =
+      final Collection? cachedCollection =
           _collectionIDToCollections[_localPathToCollectionID[path]];
           _collectionIDToCollections[_localPathToCollectionID[path]];
       if (cachedCollection != null &&
       if (cachedCollection != null &&
           !cachedCollection.isDeleted &&
           !cachedCollection.isDeleted &&
-          cachedCollection.owner.id == _config.getUserID()) {
+          cachedCollection.owner?.id == _config.getUserID()) {
         return cachedCollection;
         return cachedCollection;
       }
       }
     }
     }
     final key = CryptoUtil.generateKey();
     final key = CryptoUtil.generateKey();
-    final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
-    final encryptedPath = CryptoUtil.encryptSync(utf8.encode(path), key);
+    final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey()!);
+    final encryptedPath =
+        CryptoUtil.encryptSync(utf8.encode(path) as Uint8List, key);
     final collection = await createAndCacheCollection(
     final collection = await createAndCacheCollection(
-      Collection(
-        null,
-        null,
-        Sodium.bin2base64(encryptedKeyData.encryptedData),
-        Sodium.bin2base64(encryptedKeyData.nonce),
-        null,
-        Sodium.bin2base64(encryptedPath.encryptedData),
-        Sodium.bin2base64(encryptedPath.nonce),
-        CollectionType.folder,
-        CollectionAttributes(
-          encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData),
-          pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce),
+      CreateRequest(
+        encryptedKey: Sodium.bin2base64(encryptedKeyData.encryptedData!),
+        keyDecryptionNonce: Sodium.bin2base64(encryptedKeyData.nonce!),
+        encryptedName: Sodium.bin2base64(encryptedPath.encryptedData!),
+        nameDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce!),
+        type: CollectionType.folder,
+        attributes: CollectionAttributes(
+          encryptedPath: Sodium.bin2base64(encryptedPath.encryptedData!),
+          pathDecryptionNonce: Sodium.bin2base64(encryptedPath.nonce!),
           version: 1,
           version: 1,
         ),
         ),
-        null,
-        null,
-        null,
       ),
       ),
     );
     );
     return collection;
     return collection;
   }
   }
 
 
   Future<void> addToCollection(int collectionID, List<File> files) async {
   Future<void> addToCollection(int collectionID, List<File> files) async {
-    final containsUploadedFile = files.firstWhere(
+    final containsUploadedFile = files.firstWhereOrNull(
           (element) => element.uploadedFileID != null,
           (element) => element.uploadedFileID != null,
-          orElse: () => null,
         ) !=
         ) !=
         null;
         null;
     if (containsUploadedFile) {
     if (containsUploadedFile) {
@@ -808,13 +814,13 @@ class CollectionsService {
         file.collectionID = collectionID;
         file.collectionID = collectionID;
         final encryptedKeyData =
         final encryptedKeyData =
             CryptoUtil.encryptSync(key, getCollectionKey(collectionID));
             CryptoUtil.encryptSync(key, getCollectionKey(collectionID));
-        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
-        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
+        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
+        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
         params["files"].add(
         params["files"].add(
           CollectionFileItem(
           CollectionFileItem(
-            file.uploadedFileID,
-            file.encryptedKey,
-            file.keyDecryptionNonce,
+            file.uploadedFileID!,
+            file.encryptedKey!,
+            file.keyDecryptionNonce!,
           ).toMap(),
           ).toMap(),
         );
         );
       }
       }
@@ -834,13 +840,13 @@ class CollectionsService {
 
 
   Future<File> linkLocalFileToExistingUploadedFileInAnotherCollection(
   Future<File> linkLocalFileToExistingUploadedFileInAnotherCollection(
     int destCollectionID, {
     int destCollectionID, {
-    @required File localFileToUpload,
-    @required File existingUploadedFile,
+    required File localFileToUpload,
+    required File existingUploadedFile,
   }) async {
   }) async {
     final params = <String, dynamic>{};
     final params = <String, dynamic>{};
     params["collectionID"] = destCollectionID;
     params["collectionID"] = destCollectionID;
     params["files"] = [];
     params["files"] = [];
-    final int uploadedFileID = existingUploadedFile.uploadedFileID;
+    final int uploadedFileID = existingUploadedFile.uploadedFileID!;
 
 
     // encrypt the fileKey with destination collection's key
     // encrypt the fileKey with destination collection's key
     final fileKey = decryptFileKey(existingUploadedFile);
     final fileKey = decryptFileKey(existingUploadedFile);
@@ -848,15 +854,15 @@ class CollectionsService {
         CryptoUtil.encryptSync(fileKey, getCollectionKey(destCollectionID));
         CryptoUtil.encryptSync(fileKey, getCollectionKey(destCollectionID));
 
 
     localFileToUpload.encryptedKey =
     localFileToUpload.encryptedKey =
-        Sodium.bin2base64(encryptedKeyData.encryptedData);
+        Sodium.bin2base64(encryptedKeyData.encryptedData!);
     localFileToUpload.keyDecryptionNonce =
     localFileToUpload.keyDecryptionNonce =
-        Sodium.bin2base64(encryptedKeyData.nonce);
+        Sodium.bin2base64(encryptedKeyData.nonce!);
 
 
     params["files"].add(
     params["files"].add(
       CollectionFileItem(
       CollectionFileItem(
         uploadedFileID,
         uploadedFileID,
-        localFileToUpload.encryptedKey,
-        localFileToUpload.keyDecryptionNonce,
+        localFileToUpload.encryptedKey!,
+        localFileToUpload.keyDecryptionNonce!,
       ).toMap(),
       ).toMap(),
     );
     );
 
 
@@ -887,13 +893,13 @@ class CollectionsService {
             null; // So that a new entry is created in the FilesDB
             null; // So that a new entry is created in the FilesDB
         file.collectionID = toCollectionID;
         file.collectionID = toCollectionID;
         final encryptedKeyData = CryptoUtil.encryptSync(key, toCollectionKey);
         final encryptedKeyData = CryptoUtil.encryptSync(key, toCollectionKey);
-        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
-        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
+        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
+        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
         params["files"].add(
         params["files"].add(
           CollectionFileItem(
           CollectionFileItem(
-            file.uploadedFileID,
-            file.encryptedKey,
-            file.keyDecryptionNonce,
+            file.uploadedFileID!,
+            file.encryptedKey!,
+            file.keyDecryptionNonce!,
           ).toMap(),
           ).toMap(),
         );
         );
       }
       }
@@ -904,7 +910,7 @@ class CollectionsService {
         );
         );
         await _filesDB.insertMultiple(batch);
         await _filesDB.insertMultiple(batch);
         await TrashDB.instance
         await TrashDB.instance
-            .delete(batch.map((e) => e.uploadedFileID).toList());
+            .delete(batch.map((e) => e.uploadedFileID!).toList());
         Bus.instance.fire(
         Bus.instance.fire(
           CollectionUpdatedEvent(toCollectionID, batch, "restore"),
           CollectionUpdatedEvent(toCollectionID, batch, "restore"),
         );
         );
@@ -914,7 +920,7 @@ class CollectionsService {
         // but not uploaded automatically as it was trashed.
         // but not uploaded automatically as it was trashed.
         final localIDs = batch
         final localIDs = batch
             .where((e) => e.localID != null)
             .where((e) => e.localID != null)
-            .map((e) => e.localID)
+            .map((e) => e.localID!)
             .toSet()
             .toSet()
             .toList();
             .toList();
         if (localIDs.isNotEmpty) {
         if (localIDs.isNotEmpty) {
@@ -953,13 +959,13 @@ class CollectionsService {
         file.collectionID = toCollectionID;
         file.collectionID = toCollectionID;
         final encryptedKeyData =
         final encryptedKeyData =
             CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID));
             CryptoUtil.encryptSync(fileKey, getCollectionKey(toCollectionID));
-        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData);
-        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce);
+        file.encryptedKey = Sodium.bin2base64(encryptedKeyData.encryptedData!);
+        file.keyDecryptionNonce = Sodium.bin2base64(encryptedKeyData.nonce!);
         params["files"].add(
         params["files"].add(
           CollectionFileItem(
           CollectionFileItem(
-            file.uploadedFileID,
-            file.encryptedKey,
-            file.keyDecryptionNonce,
+            file.uploadedFileID!,
+            file.encryptedKey!,
+            file.keyDecryptionNonce!,
           ).toMap(),
           ).toMap(),
         );
         );
       }
       }
@@ -972,7 +978,7 @@ class CollectionsService {
     // remove files from old collection
     // remove files from old collection
     await _filesDB.removeFromCollection(
     await _filesDB.removeFromCollection(
       fromCollectionID,
       fromCollectionID,
-      files.map((e) => e.uploadedFileID).toList(),
+      files.map((e) => e.uploadedFileID!).toList(),
     );
     );
     Bus.instance.fire(
     Bus.instance.fire(
       CollectionUpdatedEvent(
       CollectionUpdatedEvent(
@@ -1038,11 +1044,9 @@ class CollectionsService {
   }
   }
 
 
   Future<Collection> createAndCacheCollection(
   Future<Collection> createAndCacheCollection(
-    Collection collection, {
     CreateRequest createRequest,
     CreateRequest createRequest,
-  }) async {
-    final dynamic payload =
-        createRequest != null ? createRequest.toJson() : collection.toMap();
+  ) async {
+    final dynamic payload = createRequest.toJson();
     return _enteDio
     return _enteDio
         .post(
         .post(
       "/collections",
       "/collections",
@@ -1051,21 +1055,19 @@ class CollectionsService {
         .then((response) async {
         .then((response) async {
       final collectionData = response.data["collection"];
       final collectionData = response.data["collection"];
       final collection = Collection.fromMap(collectionData);
       final collection = Collection.fromMap(collectionData);
-      if (createRequest != null) {
-        if (collectionData['magicMetadata'] != null) {
-          final decryptionKey =
-              _getAndCacheDecryptedKey(collection, source: "create");
-          final utfEncodedMmd = await CryptoUtil.decryptChaCha(
-            Sodium.base642bin(collectionData['magicMetadata']['data']),
-            decryptionKey,
-            Sodium.base642bin(collectionData['magicMetadata']['header']),
-          );
-          collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
-          collection.mMdVersion = collectionData['magicMetadata']['version'];
-          collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
-            collection.mMdEncodedJson,
-          );
-        }
+      if (collectionData['magicMetadata'] != null) {
+        final decryptionKey =
+            _getAndCacheDecryptedKey(collection, source: "create");
+        final utfEncodedMmd = await CryptoUtil.decryptChaCha(
+          Sodium.base642bin(collectionData['magicMetadata']['data']),
+          decryptionKey,
+          Sodium.base642bin(collectionData['magicMetadata']['header']),
+        );
+        collection.mMdEncodedJson = utf8.decode(utfEncodedMmd);
+        collection.mMdVersion = collectionData['magicMetadata']['version'];
+        collection.magicMetadata = CollectionMagicMetadata.fromEncodedJson(
+          collection.mMdEncodedJson,
+        );
       }
       }
       return _cacheCollectionAttributes(collection);
       return _cacheCollectionAttributes(collection);
     });
     });
@@ -1076,7 +1078,7 @@ class CollectionsService {
         _getCollectionWithDecryptedName(collection);
         _getCollectionWithDecryptedName(collection);
     if (collection.attributes.encryptedPath != null &&
     if (collection.attributes.encryptedPath != null &&
         !collection.isDeleted &&
         !collection.isDeleted &&
-        collection.owner.id == _config.getUserID()) {
+        collection.owner?.id == _config.getUserID()) {
       _localPathToCollectionID[decryptCollectionPath(collection)] =
       _localPathToCollectionID[decryptCollectionPath(collection)] =
           collection.id;
           collection.id;
     }
     }
@@ -1090,9 +1092,9 @@ class CollectionsService {
         : _config.getKey();
         : _config.getKey();
     return utf8.decode(
     return utf8.decode(
       CryptoUtil.decryptSync(
       CryptoUtil.decryptSync(
-        Sodium.base642bin(collection.attributes.encryptedPath),
+        Sodium.base642bin(collection.attributes.encryptedPath!),
         key,
         key,
-        Sodium.base642bin(collection.attributes.pathDecryptionNonce),
+        Sodium.base642bin(collection.attributes.pathDecryptionNonce!),
       ),
       ),
     );
     );
   }
   }
@@ -1106,7 +1108,7 @@ class CollectionsService {
       return collection.copyWith(name: "Deleted Album");
       return collection.copyWith(name: "Deleted Album");
     }
     }
     if (collection.encryptedName != null &&
     if (collection.encryptedName != null &&
-        collection.encryptedName.isNotEmpty) {
+        collection.encryptedName!.isNotEmpty) {
       String name;
       String name;
       try {
       try {
         final collectionKey = _getAndCacheDecryptedKey(
         final collectionKey = _getAndCacheDecryptedKey(
@@ -1114,9 +1116,9 @@ class CollectionsService {
           source: "Name",
           source: "Name",
         );
         );
         final result = CryptoUtil.decryptSync(
         final result = CryptoUtil.decryptSync(
-          Sodium.base642bin(collection.encryptedName),
+          Sodium.base642bin(collection.encryptedName!),
           collectionKey,
           collectionKey,
-          Sodium.base642bin(collection.nameDecryptionNonce),
+          Sodium.base642bin(collection.nameDecryptionNonce!),
         );
         );
         name = utf8.decode(result);
         name = utf8.decode(result);
       } catch (e, s) {
       } catch (e, s) {

+ 7 - 8
lib/services/deduplication_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/network.dart';
 import 'package:photos/core/network.dart';
@@ -76,16 +74,17 @@ class DeduplicationService {
       int mostFrequentCreationTime = 0, mostFrequentCreationTimeCount = 0;
       int mostFrequentCreationTime = 0, mostFrequentCreationTimeCount = 0;
       // Counts the frequency of creationTimes within the supposed duplicates
       // Counts the frequency of creationTimes within the supposed duplicates
       for (final file in dupe.files) {
       for (final file in dupe.files) {
-        if (creationTimeCounter.containsKey(file.creationTime)) {
-          creationTimeCounter[file.creationTime]++;
+        if (creationTimeCounter.containsKey(file.creationTime!)) {
+          creationTimeCounter[file.creationTime!] =
+              creationTimeCounter[file.creationTime!]! + 1;
         } else {
         } else {
-          creationTimeCounter[file.creationTime] = 0;
+          creationTimeCounter[file.creationTime!] = 0;
         }
         }
-        if (creationTimeCounter[file.creationTime] >
+        if (creationTimeCounter[file.creationTime]! >
             mostFrequentCreationTimeCount) {
             mostFrequentCreationTimeCount) {
           mostFrequentCreationTimeCount =
           mostFrequentCreationTimeCount =
-              creationTimeCounter[file.creationTime];
-          mostFrequentCreationTime = file.creationTime;
+              creationTimeCounter[file.creationTime]!;
+          mostFrequentCreationTime = file.creationTime!;
         }
         }
         files.add(file);
         files.add(file);
       }
       }

+ 28 - 32
lib/services/favorites_service.dart

@@ -1,7 +1,6 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:convert';
 import 'dart:convert';
+import 'dart:typed_data';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
@@ -10,6 +9,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/models/api/collection/create_request.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/services/collections_service.dart';
 import 'package:photos/services/collections_service.dart';
@@ -17,13 +17,14 @@ import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/utils/crypto_util.dart';
 import 'package:photos/utils/crypto_util.dart';
 
 
 class FavoritesService {
 class FavoritesService {
-  Configuration _config;
-  CollectionsService _collectionsService;
-  FilesDB _filesDB;
-  int _cachedFavoritesCollectionID;
+  late Configuration _config;
+  late CollectionsService _collectionsService;
+  late FilesDB _filesDB;
+  int? _cachedFavoritesCollectionID;
   final Set<int> _cachedFavUploadedIDs = {};
   final Set<int> _cachedFavUploadedIDs = {};
   final Set<String> _cachedPendingLocalIDs = {};
   final Set<String> _cachedPendingLocalIDs = {};
-  StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
+  late StreamSubscription<CollectionUpdatedEvent>
+      _collectionUpdatesSubscription;
 
 
   FavoritesService._privateConstructor() {
   FavoritesService._privateConstructor() {
     _config = Configuration.instance;
     _config = Configuration.instance;
@@ -89,27 +90,27 @@ class FavoritesService {
 
 
   Future<bool> isFavorite(File file) async {
   Future<bool> isFavorite(File file) async {
     final collection = await _getFavoritesCollection();
     final collection = await _getFavoritesCollection();
-    if (collection == null) {
+    if (collection == null || file.uploadedFileID == null) {
       return false;
       return false;
     }
     }
     return _filesDB.doesFileExistInCollection(
     return _filesDB.doesFileExistInCollection(
-      file.uploadedFileID,
+      file.uploadedFileID!,
       collection.id,
       collection.id,
     );
     );
   }
   }
 
 
-  void _updateFavoriteFilesCache(List<File> files, {@required bool favFlag}) {
+  void _updateFavoriteFilesCache(List<File> files, {required bool favFlag}) {
     final Set<int> updatedIDs = {};
     final Set<int> updatedIDs = {};
     final Set<String> localIDs = {};
     final Set<String> localIDs = {};
     for (var file in files) {
     for (var file in files) {
       if (file.uploadedFileID != null) {
       if (file.uploadedFileID != null) {
-        updatedIDs.add(file.uploadedFileID);
+        updatedIDs.add(file.uploadedFileID!);
       } else if (file.localID != null || file.localID != "") {
       } else if (file.localID != null || file.localID != "") {
         /* Note: Favorite un-uploaded files
         /* Note: Favorite un-uploaded files
         For such files, as we don't have uploaded IDs yet, we will cache
         For such files, as we don't have uploaded IDs yet, we will cache
         cache the local ID for showing the fav icon in the gallery
         cache the local ID for showing the fav icon in the gallery
          */
          */
-        localIDs.add(file.localID);
+        localIDs.add(file.localID!);
       }
       }
     }
     }
     if (favFlag) {
     if (favFlag) {
@@ -134,7 +135,7 @@ class FavoritesService {
   }
   }
 
 
   Future<void> updateFavorites(List<File> files, bool favFlag) async {
   Future<void> updateFavorites(List<File> files, bool favFlag) async {
-    final int currentUserID = Configuration.instance.getUserID();
+    final int currentUserID = Configuration.instance.getUserID()!;
     if (files.any((f) => f.uploadedFileID == null)) {
     if (files.any((f) => f.uploadedFileID == null)) {
       throw AssertionError("Can only favorite uploaded items");
       throw AssertionError("Can only favorite uploaded items");
     }
     }
@@ -161,11 +162,11 @@ class FavoritesService {
     _updateFavoriteFilesCache([file], favFlag: false);
     _updateFavoriteFilesCache([file], favFlag: false);
   }
   }
 
 
-  Future<Collection> _getFavoritesCollection() async {
+  Future<Collection?> _getFavoritesCollection() async {
     if (_cachedFavoritesCollectionID == null) {
     if (_cachedFavoritesCollectionID == null) {
       final collections = _collectionsService.getActiveCollections();
       final collections = _collectionsService.getActiveCollections();
       for (final collection in collections) {
       for (final collection in collections) {
-        if (collection.owner.id == _config.getUserID() &&
+        if (collection.owner!.id == _config.getUserID() &&
             collection.type == CollectionType.favorites) {
             collection.type == CollectionType.favorites) {
           _cachedFavoritesCollectionID = collection.id;
           _cachedFavoritesCollectionID = collection.id;
           return collection;
           return collection;
@@ -173,30 +174,25 @@ class FavoritesService {
       }
       }
       return null;
       return null;
     }
     }
-    return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID);
+    return _collectionsService.getCollectionByID(_cachedFavoritesCollectionID!);
   }
   }
 
 
   Future<int> _getOrCreateFavoriteCollectionID() async {
   Future<int> _getOrCreateFavoriteCollectionID() async {
     if (_cachedFavoritesCollectionID != null) {
     if (_cachedFavoritesCollectionID != null) {
-      return _cachedFavoritesCollectionID;
+      return _cachedFavoritesCollectionID!;
     }
     }
     final key = CryptoUtil.generateKey();
     final key = CryptoUtil.generateKey();
-    final encryptedKeyData = CryptoUtil.encryptSync(key, _config.getKey());
-    final encryptedName = CryptoUtil.encryptSync(utf8.encode("Favorites"), key);
+    final encKey = CryptoUtil.encryptSync(key, _config.getKey()!);
+    final encName =
+        CryptoUtil.encryptSync(utf8.encode("Favorites") as Uint8List, key);
     final collection = await _collectionsService.createAndCacheCollection(
     final collection = await _collectionsService.createAndCacheCollection(
-      Collection(
-        null,
-        null,
-        Sodium.bin2base64(encryptedKeyData.encryptedData),
-        Sodium.bin2base64(encryptedKeyData.nonce),
-        null,
-        Sodium.bin2base64(encryptedName.encryptedData),
-        Sodium.bin2base64(encryptedName.nonce),
-        CollectionType.favorites,
-        CollectionAttributes(),
-        null,
-        null,
-        null,
+      CreateRequest(
+        encryptedKey: Sodium.bin2base64(encKey.encryptedData!),
+        keyDecryptionNonce: Sodium.bin2base64(encKey.nonce!),
+        encryptedName: Sodium.bin2base64(encName.encryptedData!),
+        nameDecryptionNonce: Sodium.bin2base64(encName.nonce!),
+        type: CollectionType.favorites,
+        attributes: CollectionAttributes(),
       ),
       ),
     );
     );
     _cachedFavoritesCollectionID = collection.id;
     _cachedFavoritesCollectionID = collection.id;

+ 34 - 30
lib/services/file_magic_service.dart

@@ -1,6 +1,5 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
+import 'dart:typed_data';
 
 
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
 import 'package:flutter_sodium/flutter_sodium.dart';
@@ -22,8 +21,8 @@ import 'package:photos/utils/file_download_util.dart';
 
 
 class FileMagicService {
 class FileMagicService {
   final _logger = Logger("FileMagicService");
   final _logger = Logger("FileMagicService");
-  Dio _enteDio;
-  FilesDB _filesDB;
+  late Dio _enteDio;
+  late FilesDB _filesDB;
 
 
   FileMagicService._privateConstructor() {
   FileMagicService._privateConstructor() {
     _filesDB = FilesDB.instance;
     _filesDB = FilesDB.instance;
@@ -59,12 +58,12 @@ class FileMagicService {
 
 
   Future<void> updatePublicMagicMetadata(
   Future<void> updatePublicMagicMetadata(
     List<File> files,
     List<File> files,
-    Map<String, dynamic> newMetadataUpdate, {
-    Map<int, Map<String, dynamic>> metadataUpdateMap,
+    Map<String, dynamic>? newMetadataUpdate, {
+    Map<int, Map<String, dynamic>>? metadataUpdateMap,
   }) async {
   }) async {
     final params = <String, dynamic>{};
     final params = <String, dynamic>{};
     params['metadataList'] = [];
     params['metadataList'] = [];
-    final int ownerID = Configuration.instance.getUserID();
+    final int ownerID = Configuration.instance.getUserID()!;
     try {
     try {
       for (final file in files) {
       for (final file in files) {
         if (file.uploadedFileID == null) {
         if (file.uploadedFileID == null) {
@@ -85,8 +84,8 @@ class FileMagicService {
           "can not apply empty updates",
           "can not apply empty updates",
         );
         );
         final Map<String, dynamic> jsonToUpdate =
         final Map<String, dynamic> jsonToUpdate =
-            jsonDecode(file.pubMmdEncodedJson);
-        newUpdates.forEach((key, value) {
+            jsonDecode(file.pubMmdEncodedJson ?? '{}');
+        newUpdates!.forEach((key, value) {
           jsonToUpdate[key] = value;
           jsonToUpdate[key] = value;
         });
         });
 
 
@@ -96,17 +95,17 @@ class FileMagicService {
 
 
         final fileKey = decryptFileKey(file);
         final fileKey = decryptFileKey(file);
         final encryptedMMd = await CryptoUtil.encryptChaCha(
         final encryptedMMd = await CryptoUtil.encryptChaCha(
-          utf8.encode(jsonEncode(jsonToUpdate)),
+          utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
           fileKey,
           fileKey,
         );
         );
         params['metadataList'].add(
         params['metadataList'].add(
           UpdateMagicMetadataRequest(
           UpdateMagicMetadataRequest(
-            id: file.uploadedFileID,
+            id: file.uploadedFileID!,
             magicMetadata: MetadataRequest(
             magicMetadata: MetadataRequest(
               version: file.pubMmdVersion,
               version: file.pubMmdVersion,
               count: jsonToUpdate.length,
               count: jsonToUpdate.length,
-              data: Sodium.bin2base64(encryptedMMd.encryptedData),
-              header: Sodium.bin2base64(encryptedMMd.header),
+              data: Sodium.bin2base64(encryptedMMd.encryptedData!),
+              header: Sodium.bin2base64(encryptedMMd.header!),
             ),
             ),
           ),
           ),
         );
         );
@@ -119,7 +118,7 @@ class FileMagicService {
       await _filesDB.insertMultiple(files);
       await _filesDB.insertMultiple(files);
       RemoteSyncService.instance.sync(silently: true);
       RemoteSyncService.instance.sync(silently: true);
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response != null && e.response.statusCode == 409) {
+      if (e.response != null && e.response!.statusCode == 409) {
         RemoteSyncService.instance.sync(silently: true);
         RemoteSyncService.instance.sync(silently: true);
       }
       }
       rethrow;
       rethrow;
@@ -134,7 +133,7 @@ class FileMagicService {
     Map<String, dynamic> newMetadataUpdate,
     Map<String, dynamic> newMetadataUpdate,
   ) async {
   ) async {
     final params = <String, dynamic>{};
     final params = <String, dynamic>{};
-    final int ownerID = Configuration.instance.getUserID();
+    final int ownerID = Configuration.instance.getUserID()!;
     final batchedFiles = files.chunks(batchSize);
     final batchedFiles = files.chunks(batchSize);
     try {
     try {
       for (final batch in batchedFiles) {
       for (final batch in batchedFiles) {
@@ -151,7 +150,7 @@ class FileMagicService {
           // current update is simple replace. This will be enhanced in the future,
           // current update is simple replace. This will be enhanced in the future,
           // as required.
           // as required.
           final Map<String, dynamic> jsonToUpdate =
           final Map<String, dynamic> jsonToUpdate =
-              jsonDecode(file.mMdEncodedJson);
+              jsonDecode(file.mMdEncodedJson ?? '{}');
           newMetadataUpdate.forEach((key, value) {
           newMetadataUpdate.forEach((key, value) {
             jsonToUpdate[key] = value;
             jsonToUpdate[key] = value;
           });
           });
@@ -162,17 +161,17 @@ class FileMagicService {
 
 
           final fileKey = decryptFileKey(file);
           final fileKey = decryptFileKey(file);
           final encryptedMMd = await CryptoUtil.encryptChaCha(
           final encryptedMMd = await CryptoUtil.encryptChaCha(
-            utf8.encode(jsonEncode(jsonToUpdate)),
+            utf8.encode(jsonEncode(jsonToUpdate)) as Uint8List,
             fileKey,
             fileKey,
           );
           );
           params['metadataList'].add(
           params['metadataList'].add(
             UpdateMagicMetadataRequest(
             UpdateMagicMetadataRequest(
-              id: file.uploadedFileID,
+              id: file.uploadedFileID!,
               magicMetadata: MetadataRequest(
               magicMetadata: MetadataRequest(
                 version: file.mMdVersion,
                 version: file.mMdVersion,
                 count: jsonToUpdate.length,
                 count: jsonToUpdate.length,
-                data: Sodium.bin2base64(encryptedMMd.encryptedData),
-                header: Sodium.bin2base64(encryptedMMd.header),
+                data: Sodium.bin2base64(encryptedMMd.encryptedData!),
+                header: Sodium.bin2base64(encryptedMMd.header!),
               ),
               ),
             ),
             ),
           );
           );
@@ -187,7 +186,7 @@ class FileMagicService {
       // should be eventually synced after remote sync has completed
       // should be eventually synced after remote sync has completed
       RemoteSyncService.instance.sync(silently: true);
       RemoteSyncService.instance.sync(silently: true);
     } on DioError catch (e) {
     } on DioError catch (e) {
-      if (e.response != null && e.response.statusCode == 409) {
+      if (e.response != null && e.response!.statusCode == 409) {
         RemoteSyncService.instance.sync(silently: true);
         RemoteSyncService.instance.sync(silently: true);
       }
       }
       rethrow;
       rethrow;
@@ -200,9 +199,9 @@ class FileMagicService {
 
 
 class UpdateMagicMetadataRequest {
 class UpdateMagicMetadataRequest {
   final int id;
   final int id;
-  final MetadataRequest magicMetadata;
+  final MetadataRequest? magicMetadata;
 
 
-  UpdateMagicMetadataRequest({this.id, this.magicMetadata});
+  UpdateMagicMetadataRequest({required this.id, required this.magicMetadata});
 
 
   factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
   factory UpdateMagicMetadataRequest.fromJson(dynamic json) {
     return UpdateMagicMetadataRequest(
     return UpdateMagicMetadataRequest(
@@ -217,19 +216,24 @@ class UpdateMagicMetadataRequest {
     final map = <String, dynamic>{};
     final map = <String, dynamic>{};
     map['id'] = id;
     map['id'] = id;
     if (magicMetadata != null) {
     if (magicMetadata != null) {
-      map['magicMetadata'] = magicMetadata.toJson();
+      map['magicMetadata'] = magicMetadata!.toJson();
     }
     }
     return map;
     return map;
   }
   }
 }
 }
 
 
 class MetadataRequest {
 class MetadataRequest {
-  int version;
-  int count;
-  String data;
-  String header;
-
-  MetadataRequest({this.version, this.count, this.data, this.header});
+  int? version;
+  int? count;
+  String? data;
+  String? header;
+
+  MetadataRequest({
+    required this.version,
+    required this.count,
+    required this.data,
+    required this.header,
+  });
 
 
   MetadataRequest.fromJson(dynamic json) {
   MetadataRequest.fromJson(dynamic json) {
     version = json['version'];
     version = json['version'];

+ 4 - 5
lib/services/hidden_service.dart

@@ -24,7 +24,7 @@ extension HiddenService on CollectionsService {
   // collection
   // collection
   Future<Collection> getDefaultHiddenCollection() async {
   Future<Collection> getDefaultHiddenCollection() async {
     if (cachedDefaultHiddenCollection != null) {
     if (cachedDefaultHiddenCollection != null) {
-      return cachedDefaultHiddenCollection;
+      return cachedDefaultHiddenCollection!;
     }
     }
     final int userID = config.getUserID()!;
     final int userID = config.getUserID()!;
     final Collection? defaultHidden =
     final Collection? defaultHidden =
@@ -33,12 +33,12 @@ extension HiddenService on CollectionsService {
     );
     );
     if (defaultHidden != null) {
     if (defaultHidden != null) {
       cachedDefaultHiddenCollection = defaultHidden;
       cachedDefaultHiddenCollection = defaultHidden;
-      return cachedDefaultHiddenCollection;
+      return cachedDefaultHiddenCollection!;
     }
     }
     final Collection createdHiddenCollection =
     final Collection createdHiddenCollection =
         await _createDefaultHiddenAlbum();
         await _createDefaultHiddenAlbum();
     cachedDefaultHiddenCollection = createdHiddenCollection;
     cachedDefaultHiddenCollection = createdHiddenCollection;
-    return cachedDefaultHiddenCollection;
+    return cachedDefaultHiddenCollection!;
   }
   }
 
 
   Future<bool> hideFiles(
   Future<bool> hideFiles(
@@ -105,8 +105,7 @@ extension HiddenService on CollectionsService {
       subType: subTypeDefaultHidden,
       subType: subTypeDefaultHidden,
     );
     );
     _logger.info("Creating Hidden Collection");
     _logger.info("Creating Hidden Collection");
-    final collection =
-        await createAndCacheCollection(null, createRequest: createRequest);
+    final collection = await createAndCacheCollection(createRequest);
     _logger.info("Creating Hidden Collection Created Successfully");
     _logger.info("Creating Hidden Collection Created Successfully");
     final Collection collectionFromServer =
     final Collection collectionFromServer =
         await fetchCollectionByID(collection.id);
         await fetchCollectionByID(collection.id);

+ 21 - 18
lib/services/ignored_files_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
@@ -17,22 +15,22 @@ class IgnoredFilesService {
   static final IgnoredFilesService instance =
   static final IgnoredFilesService instance =
       IgnoredFilesService._privateConstructor();
       IgnoredFilesService._privateConstructor();
 
 
-  Future<Set<String>> _ignoredIDs;
+  Future<Set<String>>? _ignoredIDs;
 
 
   Future<Set<String>> get ignoredIDs async {
   Future<Set<String>> get ignoredIDs async {
     // lazily instantiate the db the first time it is accessed
     // lazily instantiate the db the first time it is accessed
     _ignoredIDs ??= _loadExistingIDs();
     _ignoredIDs ??= _loadExistingIDs();
-    return _ignoredIDs;
+    return _ignoredIDs!;
   }
   }
 
 
   Future<void> cacheAndInsert(List<IgnoredFile> ignoredFiles) async {
   Future<void> cacheAndInsert(List<IgnoredFile> ignoredFiles) async {
     final existingIDs = await ignoredIDs;
     final existingIDs = await ignoredIDs;
-    existingIDs.addAll(
-      ignoredFiles
-          .map((e) => _idForIgnoredFile(e))
-          .where((id) => id != null)
-          .toSet(),
-    );
+    for (IgnoredFile iFile in ignoredFiles) {
+      final id = _idForIgnoredFile(iFile);
+      if (id != null) {
+        existingIDs.add(id);
+      }
+    }
     return _db.insertMultiple(ignoredFiles);
     return _db.insertMultiple(ignoredFiles);
   }
   }
 
 
@@ -61,7 +59,8 @@ class IgnoredFilesService {
       if (!shouldSkipUpload(currentlyIgnoredIDs, file)) {
       if (!shouldSkipUpload(currentlyIgnoredIDs, file)) {
         continue;
         continue;
       }
       }
-      final id = _getIgnoreID(file.localID, file.deviceFolder, file.title);
+      // _shouldSkipUpload checks for null ignoreID
+      final id = _getIgnoreID(file.localID, file.deviceFolder, file.title)!;
       idsToRemoveFromCache.add(id);
       idsToRemoveFromCache.add(id);
       ignoredFiles.add(
       ignoredFiles.add(
         IgnoredFile(file.localID, file.title, file.deviceFolder, ""),
         IgnoredFile(file.localID, file.title, file.deviceFolder, ""),
@@ -81,14 +80,18 @@ class IgnoredFilesService {
 
 
   Future<Set<String>> _loadExistingIDs() async {
   Future<Set<String>> _loadExistingIDs() async {
     _logger.fine('loading existing IDs');
     _logger.fine('loading existing IDs');
-    final result = await _db.getAll();
-    return result
-        .map((e) => _idForIgnoredFile(e))
-        .where((id) => id != null)
-        .toSet();
+    final dbResult = await _db.getAll();
+    final Set<String> result = <String>{};
+    for (IgnoredFile iFile in dbResult) {
+      final id = _idForIgnoredFile(iFile);
+      if (id != null) {
+        result.add(id);
+      }
+    }
+    return result;
   }
   }
 
 
-  String _idForIgnoredFile(IgnoredFile ignoredFile) {
+  String? _idForIgnoredFile(IgnoredFile ignoredFile) {
     return _getIgnoreID(
     return _getIgnoreID(
       ignoredFile.localID,
       ignoredFile.localID,
       ignoredFile.deviceFolder,
       ignoredFile.deviceFolder,
@@ -102,7 +105,7 @@ class IgnoredFilesService {
   // For Android: It returns deviceFolder-title as ID for Android.
   // For Android: It returns deviceFolder-title as ID for Android.
   // For iOS, it returns localID as localID is uuid and the title or deviceFolder (aka
   // For iOS, it returns localID as localID is uuid and the title or deviceFolder (aka
   // album name) can be missing due to various reasons.
   // album name) can be missing due to various reasons.
-  String _getIgnoreID(String localID, String deviceFolder, String title) {
+  String? _getIgnoreID(String? localID, String? deviceFolder, String? title) {
     // file was not uploaded from mobile device
     // file was not uploaded from mobile device
     if (localID == null || localID.isEmpty) {
     if (localID == null || localID.isEmpty) {
       return null;
       return null;

+ 14 - 14
lib/services/local/local_sync_util.dart

@@ -1,9 +1,7 @@
-// @dart = 2.9
 import 'dart:io';
 import 'dart:io';
 import 'dart:math';
 import 'dart:math';
 
 
 import 'package:computer/computer.dart';
 import 'package:computer/computer.dart';
-import 'package:flutter/foundation.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';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
@@ -129,8 +127,10 @@ Future<LocalDiffResult> getDiffWithLocal(
   args['pathToLocalIDs'] = pathToLocalIDs;
   args['pathToLocalIDs'] = pathToLocalIDs;
   final LocalDiffResult diffResult =
   final LocalDiffResult diffResult =
       await computer.compute(_getLocalAssetsDiff, param: args);
       await computer.compute(_getLocalAssetsDiff, param: args);
-  diffResult.uniqueLocalFiles =
-      await _convertLocalAssetsToUniqueFiles(diffResult.localPathAssets);
+  if (diffResult.localPathAssets != null) {
+    diffResult.uniqueLocalFiles =
+        await _convertLocalAssetsToUniqueFiles(diffResult.localPathAssets!);
+  }
   return diffResult;
   return diffResult;
 }
 }
 
 
@@ -211,12 +211,12 @@ Future<List<File>> _convertLocalAssetsToUniqueFiles(
 /// [needTitle] impacts the performance for fetching the actual [AssetEntity]
 /// [needTitle] impacts the performance for fetching the actual [AssetEntity]
 /// in iOS. Same is true for [containsModifiedPath]
 /// in iOS. Same is true for [containsModifiedPath]
 Future<List<AssetPathEntity>> _getGalleryList({
 Future<List<AssetPathEntity>> _getGalleryList({
-  final int updateFromTime,
-  final int updateToTime,
+  final int? updateFromTime,
+  final int? updateToTime,
   final bool containsModifiedPath = false,
   final bool containsModifiedPath = false,
   // in iOS fetching the AssetEntity title impacts performance
   // in iOS fetching the AssetEntity title impacts performance
   final bool needsTitle = true,
   final bool needsTitle = true,
-  final OrderOption orderOption,
+  final OrderOption? orderOption,
 }) async {
 }) async {
   final filterOptionGroup = FilterOptionGroup();
   final filterOptionGroup = FilterOptionGroup();
   filterOptionGroup.setOption(
   filterOptionGroup.setOption(
@@ -312,24 +312,24 @@ class LocalPathAsset {
   final String pathName;
   final String pathName;
 
 
   LocalPathAsset({
   LocalPathAsset({
-    @required this.localIDs,
-    @required this.pathName,
-    @required this.pathID,
+    required this.localIDs,
+    required this.pathName,
+    required this.pathID,
   });
   });
 }
 }
 
 
 class LocalDiffResult {
 class LocalDiffResult {
   // unique localPath Assets.
   // unique localPath Assets.
-  final List<LocalPathAsset> localPathAssets;
+  final List<LocalPathAsset>? localPathAssets;
 
 
   // set of File object created from localPathAssets
   // set of File object created from localPathAssets
-  List<File> uniqueLocalFiles;
+  List<File>? uniqueLocalFiles;
 
 
   // newPathToLocalIDs represents new entries which needs to be synced to
   // newPathToLocalIDs represents new entries which needs to be synced to
   // the local db
   // the local db
-  final Map<String, Set<String>> newPathToLocalIDs;
+  final Map<String, Set<String>>? newPathToLocalIDs;
 
 
-  final Map<String, Set<String>> deletePathToLocalIDs;
+  final Map<String, Set<String>>? deletePathToLocalIDs;
 
 
   LocalDiffResult({
   LocalDiffResult({
     this.uniqueLocalFiles,
     this.uniqueLocalFiles,

+ 5 - 7
lib/services/local_authentication_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:local_auth/local_auth.dart';
 import 'package:local_auth/local_auth.dart';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
@@ -18,9 +16,9 @@ class LocalAuthenticationService {
     String infoMessage,
     String infoMessage,
   ) async {
   ) async {
     if (await _isLocalAuthSupportedOnDevice()) {
     if (await _isLocalAuthSupportedOnDevice()) {
-      AppLock.of(context).setEnabled(false);
+      AppLock.of(context)!.setEnabled(false);
       final result = await requestAuthentication(infoMessage);
       final result = await requestAuthentication(infoMessage);
-      AppLock.of(context).setEnabled(
+      AppLock.of(context)!.setEnabled(
         Configuration.instance.shouldShowLockScreen(),
         Configuration.instance.shouldShowLockScreen(),
       );
       );
       if (!result) {
       if (!result) {
@@ -41,17 +39,17 @@ class LocalAuthenticationService {
     String errorDialogTitle = "",
     String errorDialogTitle = "",
   ]) async {
   ]) async {
     if (await _isLocalAuthSupportedOnDevice()) {
     if (await _isLocalAuthSupportedOnDevice()) {
-      AppLock.of(context).disable();
+      AppLock.of(context)!.disable();
       final result = await requestAuthentication(
       final result = await requestAuthentication(
         infoMessage,
         infoMessage,
       );
       );
       if (result) {
       if (result) {
-        AppLock.of(context).setEnabled(shouldEnableLockScreen);
+        AppLock.of(context)!.setEnabled(shouldEnableLockScreen);
         await Configuration.instance
         await Configuration.instance
             .setShouldShowLockScreen(shouldEnableLockScreen);
             .setShouldShowLockScreen(shouldEnableLockScreen);
         return true;
         return true;
       } else {
       } else {
-        AppLock.of(context)
+        AppLock.of(context)!
             .setEnabled(Configuration.instance.shouldShowLockScreen());
             .setEnabled(Configuration.instance.shouldShowLockScreen());
       }
       }
     } else {
     } else {

+ 23 - 23
lib/services/local_file_update_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:core';
 import 'dart:core';
 import 'dart:io';
 import 'dart:io';
@@ -20,15 +18,15 @@ import 'package:shared_preferences/shared_preferences.dart';
 // LocalFileUpdateService tracks all the potential local file IDs which have
 // LocalFileUpdateService tracks all the potential local file IDs which have
 // changed/modified on the device and needed to be uploaded again.
 // changed/modified on the device and needed to be uploaded again.
 class LocalFileUpdateService {
 class LocalFileUpdateService {
-  FileUpdationDB _fileUpdationDB;
-  SharedPreferences _prefs;
-  Logger _logger;
+  late FileUpdationDB _fileUpdationDB;
+  late SharedPreferences _prefs;
+  late Logger _logger;
   static const isLocationMigrationComplete = "fm_isLocationMigrationComplete";
   static const isLocationMigrationComplete = "fm_isLocationMigrationComplete";
   static const isLocalImportDone = "fm_IsLocalImportDone";
   static const isLocalImportDone = "fm_IsLocalImportDone";
   static const isBadCreationTimeImportDone = 'fm_badCreationTime';
   static const isBadCreationTimeImportDone = 'fm_badCreationTime';
   static const isBadCreationTimeMigrationComplete =
   static const isBadCreationTimeMigrationComplete =
       'fm_badCreationTimeCompleted';
       'fm_badCreationTimeCompleted';
-  Completer<void> _existingMigration;
+  Completer<void>? _existingMigration;
 
 
   LocalFileUpdateService._privateConstructor() {
   LocalFileUpdateService._privateConstructor() {
     _logger = Logger((LocalFileUpdateService).toString());
     _logger = Logger((LocalFileUpdateService).toString());
@@ -43,13 +41,13 @@ class LocalFileUpdateService {
       LocalFileUpdateService._privateConstructor();
       LocalFileUpdateService._privateConstructor();
 
 
   bool isBadCreationMigrationCompleted() {
   bool isBadCreationMigrationCompleted() {
-    return _prefs.get(isBadCreationTimeMigrationComplete) ?? false;
+    return (_prefs.getBool(isBadCreationTimeMigrationComplete) ?? false);
   }
   }
 
 
   Future<void> markUpdatedFilesForReUpload() async {
   Future<void> markUpdatedFilesForReUpload() async {
     if (_existingMigration != null) {
     if (_existingMigration != null) {
       _logger.info("migration is already in progress, skipping");
       _logger.info("migration is already in progress, skipping");
-      return _existingMigration.future;
+      return _existingMigration!.future;
     }
     }
     _existingMigration = Completer<void>();
     _existingMigration = Completer<void>();
     try {
     try {
@@ -107,11 +105,10 @@ class LocalFileUpdateService {
       MediaUploadData uploadData;
       MediaUploadData uploadData;
       try {
       try {
         uploadData = await getUploadData(file);
         uploadData = await getUploadData(file);
-        if (uploadData != null &&
-            uploadData.hashData != null &&
+        if (uploadData.hashData != null &&
             file.hash != null &&
             file.hash != null &&
-            (file.hash == uploadData.hashData.fileHash ||
-                file.hash == uploadData.hashData.zipHash)) {
+            (file.hash == uploadData.hashData!.fileHash ||
+                file.hash == uploadData.hashData!.zipHash)) {
           _logger.info("Skip file update as hash matched ${file.tag}");
           _logger.info("Skip file update as hash matched ${file.tag}");
         } else {
         } else {
           _logger.info(
           _logger.info(
@@ -119,15 +116,15 @@ class LocalFileUpdateService {
           );
           );
           await clearCache(file);
           await clearCache(file);
           await FilesDB.instance.updateUploadedFile(
           await FilesDB.instance.updateUploadedFile(
-            file.localID,
+            file.localID!,
             file.title,
             file.title,
             file.location,
             file.location,
-            file.creationTime,
-            file.modificationTime,
+            file.creationTime!,
+            file.modificationTime!,
             null,
             null,
           );
           );
         }
         }
-        processedIDs.add(file.localID);
+        processedIDs.add(file.localID!);
       } catch (e) {
       } catch (e) {
         _logger.severe("Failed to get file uploadData", e);
         _logger.severe("Failed to get file uploadData", e);
       } finally {}
       } finally {}
@@ -144,10 +141,8 @@ class LocalFileUpdateService {
     // delete the file from app's internal cache if it was copied to app
     // delete the file from app's internal cache if it was copied to app
     // for upload. Shared Media should only be cleared when the upload
     // for upload. Shared Media should only be cleared when the upload
     // succeeds.
     // succeeds.
-    if (Platform.isIOS &&
-        mediaUploadData != null &&
-        mediaUploadData.sourceFile != null) {
-      await mediaUploadData.sourceFile.delete();
+    if (Platform.isIOS && mediaUploadData.sourceFile != null) {
+      await mediaUploadData.sourceFile?.delete();
     }
     }
     return mediaUploadData;
     return mediaUploadData;
   }
   }
@@ -165,8 +160,13 @@ class LocalFileUpdateService {
         FileUpdationDB.badCreationTime,
         FileUpdationDB.badCreationTime,
       );
       );
       if (generatedIDs.isNotEmpty) {
       if (generatedIDs.isNotEmpty) {
-        final List<int> genIdIntList =
-            generatedIDs.map((e) => int.tryParse(e)).toList();
+        final List<int> genIdIntList = [];
+        for (String genIdString in generatedIDs) {
+          final int? genIdInt = int.tryParse(genIdString);
+          if (genIdInt != null) {
+            genIdIntList.add(genIdInt);
+          }
+        }
 
 
         final filesWithBadTime =
         final filesWithBadTime =
             (await FilesDB.instance.getFilesFromGeneratedIDs(genIdIntList))
             (await FilesDB.instance.getFilesFromGeneratedIDs(genIdIntList))
@@ -196,7 +196,7 @@ class LocalFileUpdateService {
     }
     }
     _logger.info('_importFilesWithBadCreationTime');
     _logger.info('_importFilesWithBadCreationTime');
     final EnteWatch watch = EnteWatch("_importFilesWithBadCreationTime");
     final EnteWatch watch = EnteWatch("_importFilesWithBadCreationTime");
-    final int ownerID = Configuration.instance.getUserID();
+    final int ownerID = Configuration.instance.getUserID()!;
     final filesGeneratedID = await FilesDB.instance
     final filesGeneratedID = await FilesDB.instance
         .getGeneratedIDForFilesOlderThan(jan011981Time, ownerID);
         .getGeneratedIDForFilesOlderThan(jan011981Time, ownerID);
     await _fileUpdationDB.insertMultiple(
     await _fileUpdationDB.insertMultiple(

+ 54 - 17
lib/services/local_sync_service.dart

@@ -1,11 +1,8 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
 import 'package:computer/computer.dart';
 import 'package:computer/computer.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
-import 'package:flutter/widgets.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';
 import 'package:photos/core/configuration.dart';
 import 'package:photos/core/configuration.dart';
@@ -28,8 +25,8 @@ class LocalSyncService {
   final _logger = Logger("LocalSyncService");
   final _logger = Logger("LocalSyncService");
   final _db = FilesDB.instance;
   final _db = FilesDB.instance;
   final Computer _computer = Computer();
   final Computer _computer = Computer();
-  SharedPreferences _prefs;
-  Completer<void> _existingSync;
+  late SharedPreferences _prefs;
+  Completer<void>? _existingSync;
 
 
   static const kDbUpdationTimeKey = "db_updation_time";
   static const kDbUpdationTimeKey = "db_updation_time";
   static const kHasCompletedFirstImportKey = "has_completed_firstImport";
   static const kHasCompletedFirstImportKey = "has_completed_firstImport";
@@ -75,7 +72,7 @@ class LocalSyncService {
     }
     }
     if (_existingSync != null) {
     if (_existingSync != null) {
       _logger.warning("Sync already in progress, skipping.");
       _logger.warning("Sync already in progress, skipping.");
-      return _existingSync.future;
+      return _existingSync!.future;
     }
     }
     _existingSync = Completer<void>();
     _existingSync = Completer<void>();
     final existingLocalFileIDs = await _db.getExistingLocalFileIDs();
     final existingLocalFileIDs = await _db.getExistingLocalFileIDs();
@@ -118,7 +115,7 @@ class LocalSyncService {
       );
       );
     }
     }
     if (!_prefs.containsKey(kHasCompletedFirstImportKey) ||
     if (!_prefs.containsKey(kHasCompletedFirstImportKey) ||
-        !_prefs.getBool(kHasCompletedFirstImportKey)) {
+        !(_prefs.getBool(kHasCompletedFirstImportKey)!)) {
       await _prefs.setBool(kHasCompletedFirstImportKey, true);
       await _prefs.setBool(kHasCompletedFirstImportKey, true);
       // mark device collection has imported on first import
       // mark device collection has imported on first import
       await _refreshDeviceFolderCountAndCover(isFirstSync: true);
       await _refreshDeviceFolderCountAndCover(isFirstSync: true);
@@ -130,7 +127,7 @@ class LocalSyncService {
     final endTime = DateTime.now().microsecondsSinceEpoch;
     final endTime = DateTime.now().microsecondsSinceEpoch;
     final duration = Duration(microseconds: endTime - startTime);
     final duration = Duration(microseconds: endTime - startTime);
     _logger.info("Load took " + duration.inMilliseconds.toString() + "ms");
     _logger.info("Load took " + duration.inMilliseconds.toString() + "ms");
-    _existingSync.complete();
+    _existingSync?.complete();
     _existingSync = null;
     _existingSync = null;
   }
   }
 
 
@@ -202,23 +199,24 @@ class LocalSyncService {
     );
     );
     bool hasAnyMappingChanged = false;
     bool hasAnyMappingChanged = false;
     if (localDiffResult.newPathToLocalIDs?.isNotEmpty ?? false) {
     if (localDiffResult.newPathToLocalIDs?.isNotEmpty ?? false) {
-      await _db.insertPathIDToLocalIDMapping(localDiffResult.newPathToLocalIDs);
+      await _db
+          .insertPathIDToLocalIDMapping(localDiffResult.newPathToLocalIDs!);
       hasAnyMappingChanged = true;
       hasAnyMappingChanged = true;
     }
     }
     if (localDiffResult.deletePathToLocalIDs?.isNotEmpty ?? false) {
     if (localDiffResult.deletePathToLocalIDs?.isNotEmpty ?? false) {
       await _db
       await _db
-          .deletePathIDToLocalIDMapping(localDiffResult.deletePathToLocalIDs);
+          .deletePathIDToLocalIDMapping(localDiffResult.deletePathToLocalIDs!);
       hasAnyMappingChanged = true;
       hasAnyMappingChanged = true;
     }
     }
     final bool hasUnsyncedFiles =
     final bool hasUnsyncedFiles =
         localDiffResult.uniqueLocalFiles?.isNotEmpty ?? false;
         localDiffResult.uniqueLocalFiles?.isNotEmpty ?? false;
     if (hasUnsyncedFiles) {
     if (hasUnsyncedFiles) {
       await _db.insertMultiple(
       await _db.insertMultiple(
-        localDiffResult.uniqueLocalFiles,
+        localDiffResult.uniqueLocalFiles!,
         conflictAlgorithm: ConflictAlgorithm.ignore,
         conflictAlgorithm: ConflictAlgorithm.ignore,
       );
       );
       _logger.info(
       _logger.info(
-        "Inserted ${localDiffResult.uniqueLocalFiles.length} "
+        "Inserted ${localDiffResult.uniqueLocalFiles?.length} "
         "un-synced files",
         "un-synced files",
       );
       );
     }
     }
@@ -239,29 +237,54 @@ class LocalSyncService {
   }
   }
 
 
   Future<void> trackEditedFile(File file) async {
   Future<void> trackEditedFile(File file) async {
+    if (file.localID == null) {
+      debugPrint("Warning: Edit file has no localID");
+      return;
+    }
     final editedIDs = _getEditedFileIDs();
     final editedIDs = _getEditedFileIDs();
-    editedIDs.add(file.localID);
+    editedIDs.add(file.localID!);
     await _prefs.setStringList(kEditedFileIDsKey, editedIDs);
     await _prefs.setStringList(kEditedFileIDsKey, editedIDs);
   }
   }
 
 
   List<String> _getEditedFileIDs() {
   List<String> _getEditedFileIDs() {
     if (_prefs.containsKey(kEditedFileIDsKey)) {
     if (_prefs.containsKey(kEditedFileIDsKey)) {
-      return _prefs.getStringList(kEditedFileIDsKey);
+      return _prefs.getStringList(kEditedFileIDsKey)!;
     } else {
     } else {
       final List<String> editedIDs = [];
       final List<String> editedIDs = [];
       return editedIDs;
       return editedIDs;
     }
     }
   }
   }
 
 
+<<<<<<< HEAD
+=======
+  Future<void> trackDownloadedFile(String localID) async {
+    final downloadedIDs = _getDownloadedFileIDs();
+    downloadedIDs.add(localID);
+    await _prefs.setStringList(kDownloadedFileIDsKey, downloadedIDs);
+  }
+
+  List<String> _getDownloadedFileIDs() {
+    if (_prefs.containsKey(kDownloadedFileIDsKey)) {
+      return _prefs.getStringList(kDownloadedFileIDsKey)!;
+    } else {
+      return <String>[];
+    }
+  }
+
+>>>>>>> main
   Future<void> trackInvalidFile(File file) async {
   Future<void> trackInvalidFile(File file) async {
+    if (file.localID == null) {
+      debugPrint("Warning: Invalid file has no localID");
+      return;
+    }
     final invalidIDs = _getInvalidFileIDs();
     final invalidIDs = _getInvalidFileIDs();
-    invalidIDs.add(file.localID);
+    invalidIDs.add(file.localID!);
     await _prefs.setStringList(kInvalidFileIDsKey, invalidIDs);
     await _prefs.setStringList(kInvalidFileIDsKey, invalidIDs);
   }
   }
 
 
   List<String> _getInvalidFileIDs() {
   List<String> _getInvalidFileIDs() {
     if (_prefs.containsKey(kInvalidFileIDsKey)) {
     if (_prefs.containsKey(kInvalidFileIDsKey)) {
-      return _prefs.getStringList(kInvalidFileIDsKey);
+      return _prefs.getStringList(kInvalidFileIDsKey)!;
     } else {
     } else {
       return <String>[];
       return <String>[];
     }
     }
@@ -363,7 +386,9 @@ class LocalSyncService {
       );
       );
       final List<String> updatedLocalIDs = [];
       final List<String> updatedLocalIDs = [];
       for (final file in updatedFiles) {
       for (final file in updatedFiles) {
-        updatedLocalIDs.add(file.localID);
+        if (file.localID != null) {
+          updatedLocalIDs.add(file.localID!);
+        }
       }
       }
       await FileUpdationDB.instance.insertMultiple(
       await FileUpdationDB.instance.insertMultiple(
         updatedLocalIDs,
         updatedLocalIDs,
@@ -376,7 +401,19 @@ class LocalSyncService {
     // In case of iOS limit permission, this call back is fired immediately
     // In case of iOS limit permission, this call back is fired immediately
     // after file selection dialog is dismissed.
     // after file selection dialog is dismissed.
     PhotoManager.addChangeCallback((value) async {
     PhotoManager.addChangeCallback((value) async {
+<<<<<<< HEAD
       checkAndSync();
       checkAndSync();
+=======
+      _logger.info("Something changed on disk");
+      if (_existingSync != null) {
+        await _existingSync!.future;
+      }
+      if (hasGrantedLimitedPermissions()) {
+        syncAll();
+      } else {
+        sync().then((value) => _refreshDeviceFolderCountAndCover());
+      }
+>>>>>>> main
     });
     });
     PhotoManager.startChangeNotify();
     PhotoManager.startChangeNotify();
   }
   }

+ 6 - 8
lib/services/memories_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 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';
@@ -18,8 +16,8 @@ class MemoriesService extends ChangeNotifier {
   static const daysBefore = 7;
   static const daysBefore = 7;
   static const daysAfter = 1;
   static const daysAfter = 1;
 
 
-  List<Memory> _cachedMemories;
-  Future<List<Memory>> _future;
+  List<Memory>? _cachedMemories;
+  Future<List<Memory>>? _future;
 
 
   MemoriesService._privateConstructor();
   MemoriesService._privateConstructor();
 
 
@@ -45,13 +43,13 @@ class MemoriesService extends ChangeNotifier {
 
 
   Future<List<Memory>> getMemories() async {
   Future<List<Memory>> getMemories() async {
     if (_cachedMemories != null) {
     if (_cachedMemories != null) {
-      return _cachedMemories;
+      return _cachedMemories!;
     }
     }
     if (_future != null) {
     if (_future != null) {
-      return _future;
+      return _future!;
     }
     }
     _future = _fetchMemories();
     _future = _fetchMemories();
-    return _future;
+    return _future!;
   }
   }
 
 
   Future<List<Memory>> _fetchMemories() async {
   Future<List<Memory>> _fetchMemories() async {
@@ -90,7 +88,7 @@ class MemoriesService extends ChangeNotifier {
       }
       }
     }
     }
     _cachedMemories = memories;
     _cachedMemories = memories;
-    return _cachedMemories;
+    return _cachedMemories!;
   }
   }
 
 
   DateTime _getDate(DateTime present, int yearAgo) {
   DateTime _getDate(DateTime present, int yearAgo) {

+ 10 - 8
lib/services/push_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:firebase_core/firebase_core.dart';
 import 'package:firebase_core/firebase_core.dart';
 import 'package:firebase_messaging/firebase_messaging.dart';
 import 'package:firebase_messaging/firebase_messaging.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -21,7 +19,7 @@ class PushService {
   static final PushService instance = PushService._privateConstructor();
   static final PushService instance = PushService._privateConstructor();
   static final _logger = Logger("PushService");
   static final _logger = Logger("PushService");
 
 
-  SharedPreferences _prefs;
+  late SharedPreferences _prefs;
 
 
   PushService._privateConstructor();
   PushService._privateConstructor();
 
 
@@ -46,14 +44,15 @@ class PushService {
   }
   }
 
 
   Future<void> _configurePushToken() async {
   Future<void> _configurePushToken() async {
-    final fcmToken = await FirebaseMessaging.instance.getToken();
+    final String? fcmToken = await FirebaseMessaging.instance.getToken();
     final shouldForceRefreshServerToken =
     final shouldForceRefreshServerToken =
         DateTime.now().microsecondsSinceEpoch -
         DateTime.now().microsecondsSinceEpoch -
                 (_prefs.getInt(kLastFCMTokenUpdationTime) ?? 0) >
                 (_prefs.getInt(kLastFCMTokenUpdationTime) ?? 0) >
             kFCMTokenUpdationIntervalInMicroSeconds;
             kFCMTokenUpdationIntervalInMicroSeconds;
-    if (_prefs.getString(kFCMPushToken) != fcmToken ||
-        shouldForceRefreshServerToken) {
-      final apnsToken = await FirebaseMessaging.instance.getAPNSToken();
+    if (fcmToken != null &&
+        (_prefs.getString(kFCMPushToken) != fcmToken ||
+            shouldForceRefreshServerToken)) {
+      final String? apnsToken = await FirebaseMessaging.instance.getAPNSToken();
       try {
       try {
         _logger.info("Updating token on server");
         _logger.info("Updating token on server");
         await _setPushTokenOnServer(fcmToken, apnsToken);
         await _setPushTokenOnServer(fcmToken, apnsToken);
@@ -71,7 +70,10 @@ class PushService {
     }
     }
   }
   }
 
 
-  Future<void> _setPushTokenOnServer(String fcmToken, String apnsToken) async {
+  Future<void> _setPushTokenOnServer(
+    String fcmToken,
+    String? apnsToken,
+  ) async {
     await Network.instance.enteDio.post(
     await Network.instance.enteDio.post(
       "/push/token",
       "/push/token",
       data: {
       data: {

+ 40 - 36
lib/services/remote_sync_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 import 'dart:math';
 import 'dart:math';
@@ -44,8 +42,8 @@ class RemoteSyncService {
   final LocalFileUpdateService _localFileUpdateService =
   final LocalFileUpdateService _localFileUpdateService =
       LocalFileUpdateService.instance;
       LocalFileUpdateService.instance;
   int _completedUploads = 0;
   int _completedUploads = 0;
-  SharedPreferences _prefs;
-  Completer<void> _existingSync;
+  late SharedPreferences _prefs;
+  Completer<void>? _existingSync;
   bool _existingSyncSilent = false;
   bool _existingSyncSilent = false;
 
 
   static const kHasSyncedArchiveKey = "has_synced_archive";
   static const kHasSyncedArchiveKey = "has_synced_archive";
@@ -89,7 +87,7 @@ class RemoteSyncService {
       if (_existingSyncSilent == true && silently == false) {
       if (_existingSyncSilent == true && silently == false) {
         _existingSyncSilent = false;
         _existingSyncSilent = false;
       }
       }
-      return _existingSync.future;
+      return _existingSync?.future;
     }
     }
     _existingSync = Completer<void>();
     _existingSync = Completer<void>();
     _existingSyncSilent = silently;
     _existingSyncSilent = silently;
@@ -99,8 +97,7 @@ class RemoteSyncService {
       // remote-sync is done. This is done to avoid adding existing files to
       // remote-sync is done. This is done to avoid adding existing files to
       // the same or different collection when user had already uploaded them
       // the same or different collection when user had already uploaded them
       // before.
       // before.
-      final bool hasSyncedBefore =
-          _prefs.containsKey(_isFirstRemoteSyncDone) ?? false;
+      final bool hasSyncedBefore = _prefs.containsKey(_isFirstRemoteSyncDone);
       if (hasSyncedBefore) {
       if (hasSyncedBefore) {
         await syncDeviceCollectionFilesForUpload();
         await syncDeviceCollectionFilesForUpload();
       }
       }
@@ -120,7 +117,7 @@ class RemoteSyncService {
       final hasUploadedFiles = await _uploadFiles(filesToBeUploaded);
       final hasUploadedFiles = await _uploadFiles(filesToBeUploaded);
       if (hasUploadedFiles) {
       if (hasUploadedFiles) {
         await _pullDiff();
         await _pullDiff();
-        _existingSync.complete();
+        _existingSync?.complete();
         _existingSync = null;
         _existingSync = null;
         await syncDeviceCollectionFilesForUpload();
         await syncDeviceCollectionFilesForUpload();
         final hasMoreFilesToBackup = (await _getFilesToBeUploaded()).isNotEmpty;
         final hasMoreFilesToBackup = (await _getFilesToBeUploaded()).isNotEmpty;
@@ -133,11 +130,11 @@ class RemoteSyncService {
           Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
           Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
         }
         }
       } else {
       } else {
-        _existingSync.complete();
+        _existingSync?.complete();
         _existingSync = null;
         _existingSync = null;
       }
       }
     } catch (e, s) {
     } catch (e, s) {
-      _existingSync.complete();
+      _existingSync?.complete();
       _existingSync = null;
       _existingSync = null;
       // rethrow whitelisted error so that UI status can be updated correctly.
       // rethrow whitelisted error so that UI status can be updated correctly.
       if (e is UnauthorizedError ||
       if (e is UnauthorizedError ||
@@ -202,7 +199,7 @@ class RemoteSyncService {
     final diff =
     final diff =
         await _diffFetcher.getEncryptedFilesDiff(collectionID, sinceTime);
         await _diffFetcher.getEncryptedFilesDiff(collectionID, sinceTime);
     if (diff.deletedFiles.isNotEmpty) {
     if (diff.deletedFiles.isNotEmpty) {
-      final fileIDs = diff.deletedFiles.map((f) => f.uploadedFileID).toList();
+      final fileIDs = diff.deletedFiles.map((f) => f.uploadedFileID!).toList();
       final deletedFiles = (await _db.getFilesFromIDs(fileIDs)).values.toList();
       final deletedFiles = (await _db.getFilesFromIDs(fileIDs)).values.toList();
       await _db.deleteFilesFromCollection(collectionID, fileIDs);
       await _db.deleteFilesFromCollection(collectionID, fileIDs);
       Bus.instance.fire(
       Bus.instance.fire(
@@ -259,7 +256,7 @@ class RemoteSyncService {
   }
   }
 
 
   Future<void> syncDeviceCollectionFilesForUpload() async {
   Future<void> syncDeviceCollectionFilesForUpload() async {
-    final int ownerID = _config.getUserID();
+    final int ownerID = _config.getUserID()!;
 
 
     final deviceCollections = await _db.getDeviceCollections();
     final deviceCollections = await _db.getDeviceCollections();
     deviceCollections.removeWhere((element) => !element.shouldBackup);
     deviceCollections.removeWhere((element) => !element.shouldBackup);
@@ -290,7 +287,7 @@ class RemoteSyncService {
 
 
       moreFilesMarkedForBackup = true;
       moreFilesMarkedForBackup = true;
       await _db.setCollectionIDForUnMappedLocalFiles(
       await _db.setCollectionIDForUnMappedLocalFiles(
-        deviceCollection.collectionID,
+        deviceCollection.collectionID!,
         localIDsToSync,
         localIDsToSync,
       );
       );
 
 
@@ -298,7 +295,9 @@ class RemoteSyncService {
       // the collection. This can happen when a user has marked a folder
       // the collection. This can happen when a user has marked a folder
       // for sync, then un-synced it and again tries to mark if for sync.
       // for sync, then un-synced it and again tries to mark if for sync.
       final Set<String> existingMapping =
       final Set<String> existingMapping =
-          await _db.getLocalFileIDsForCollection(deviceCollection.collectionID);
+          await _db.getLocalFileIDsForCollection(
+        deviceCollection.collectionID!,
+      );
       final Set<String> commonElements =
       final Set<String> commonElements =
           localIDsToSync.intersection(existingMapping);
           localIDsToSync.intersection(existingMapping);
       if (commonElements.isNotEmpty) {
       if (commonElements.isNotEmpty) {
@@ -322,7 +321,7 @@ class RemoteSyncService {
         final List<File> newFilesToInsert = [];
         final List<File> newFilesToInsert = [];
         final Set<String> fileFoundForLocalIDs = {};
         final Set<String> fileFoundForLocalIDs = {};
         for (var existingFile in filesWithCollectionID) {
         for (var existingFile in filesWithCollectionID) {
-          final String localID = existingFile.localID;
+          final String localID = existingFile.localID!;
           if (!fileFoundForLocalIDs.contains(localID)) {
           if (!fileFoundForLocalIDs.contains(localID)) {
             existingFile.generatedID = null;
             existingFile.generatedID = null;
             existingFile.collectionID = deviceCollection.collectionID;
             existingFile.collectionID = deviceCollection.collectionID;
@@ -400,7 +399,7 @@ class RemoteSyncService {
       final List<int> entriesToDelete = [];
       final List<int> entriesToDelete = [];
       for (File pendingUpload in pendingUploads) {
       for (File pendingUpload in pendingUploads) {
         if (localIDsInOtherFileEntries.contains(pendingUpload.localID)) {
         if (localIDsInOtherFileEntries.contains(pendingUpload.localID)) {
-          entriesToDelete.add(pendingUpload.generatedID);
+          entriesToDelete.add(pendingUpload.generatedID!);
         } else {
         } else {
           pendingUpload.collectionID = null;
           pendingUpload.collectionID = null;
           entriesToUpdate.add(pendingUpload);
           entriesToUpdate.add(pendingUpload);
@@ -418,7 +417,7 @@ class RemoteSyncService {
   Future<void> _createCollectionForDevicePath(
   Future<void> _createCollectionForDevicePath(
     DeviceCollection deviceCollection,
     DeviceCollection deviceCollection,
   ) async {
   ) async {
-    int deviceCollectionID = deviceCollection.collectionID;
+    int deviceCollectionID = deviceCollection.collectionID ?? -1;
     if (deviceCollectionID != -1) {
     if (deviceCollectionID != -1) {
       final collectionByID =
       final collectionByID =
           _collectionsService.getCollectionByID(deviceCollectionID);
           _collectionsService.getCollectionByID(deviceCollectionID);
@@ -468,7 +467,7 @@ class RemoteSyncService {
   }
   }
 
 
   Future<bool> _uploadFiles(List<File> filesToBeUploaded) async {
   Future<bool> _uploadFiles(List<File> filesToBeUploaded) async {
-    final int ownerID = _config.getUserID();
+    final int ownerID = _config.getUserID()!;
     final updatedFileIDs = await _db.getUploadedFileIDsToBeUpdated(ownerID);
     final updatedFileIDs = await _db.getUploadedFileIDsToBeUpdated(ownerID);
     if (updatedFileIDs.isNotEmpty) {
     if (updatedFileIDs.isNotEmpty) {
       _logger.info("Identified ${updatedFileIDs.length} files for reupload");
       _logger.info("Identified ${updatedFileIDs.length} files for reupload");
@@ -492,7 +491,9 @@ class RemoteSyncService {
         break;
         break;
       }
       }
       final file = await _db.getUploadedFileInAnyCollection(uploadedFileID);
       final file = await _db.getUploadedFileInAnyCollection(uploadedFileID);
-      _uploadFile(file, file.collectionID, futures);
+      if (file != null) {
+        _uploadFile(file, file.collectionID!, futures);
+      }
     }
     }
 
 
     for (final file in filesToBeUploaded) {
     for (final file in filesToBeUploaded) {
@@ -504,7 +505,9 @@ class RemoteSyncService {
       // prefer existing collection ID for manually uploaded files.
       // prefer existing collection ID for manually uploaded files.
       // See https://github.com/ente-io/photos-app/pull/187
       // See https://github.com/ente-io/photos-app/pull/187
       final collectionID = file.collectionID ??
       final collectionID = file.collectionID ??
-          (await _collectionsService.getOrCreateForPath(file.deviceFolder)).id;
+          (await _collectionsService
+                  .getOrCreateForPath(file.deviceFolder ?? 'Unknown Folder'))
+              .id;
       _uploadFile(file, collectionID, futures);
       _uploadFile(file, collectionID, futures);
     }
     }
 
 
@@ -587,7 +590,7 @@ class RemoteSyncService {
         localUploadedFromDevice = 0,
         localUploadedFromDevice = 0,
         localButUpdatedOnDevice = 0,
         localButUpdatedOnDevice = 0,
         remoteNewFile = 0;
         remoteNewFile = 0;
-    final int userID = _config.getUserID();
+    final int userID = _config.getUserID()!;
     bool needsGalleryReload = false;
     bool needsGalleryReload = false;
     // this is required when same file is uploaded twice in the same
     // this is required when same file is uploaded twice in the same
     // collection. Without this check, if both remote files are part of same
     // collection. Without this check, if both remote files are part of same
@@ -599,13 +602,14 @@ class RemoteSyncService {
     for (File remoteDiff in diff) {
     for (File remoteDiff in diff) {
       // existingFile will be either set to existing collectionID+localID or
       // existingFile will be either set to existing collectionID+localID or
       // to the unclaimed aka not already linked to any uploaded file.
       // to the unclaimed aka not already linked to any uploaded file.
-      File existingFile;
+      File? existingFile;
       if (remoteDiff.generatedID != null) {
       if (remoteDiff.generatedID != null) {
         // Case [1] Check and clear local cache when uploadedFile already exist
         // Case [1] Check and clear local cache when uploadedFile already exist
         // Note: Existing file can be null here if it's replaced by the time we
         // Note: Existing file can be null here if it's replaced by the time we
         // reach here
         // reach here
-        existingFile = await _db.getFile(remoteDiff.generatedID);
-        if (_shouldClearCache(remoteDiff, existingFile)) {
+        existingFile = await _db.getFile(remoteDiff.generatedID!);
+        if (existingFile != null &&
+            _shouldClearCache(remoteDiff, existingFile)) {
           needsGalleryReload = true;
           needsGalleryReload = true;
           await clearCache(remoteDiff);
           await clearCache(remoteDiff);
         }
         }
@@ -636,9 +640,9 @@ class RemoteSyncService {
       if (existingFile == null && remoteDiff.localID != null) {
       if (existingFile == null && remoteDiff.localID != null) {
         final localFileEntries = await _db.getUnlinkedLocalMatchesForRemoteFile(
         final localFileEntries = await _db.getUnlinkedLocalMatchesForRemoteFile(
           userID,
           userID,
-          remoteDiff.localID,
+          remoteDiff.localID!,
           remoteDiff.fileType,
           remoteDiff.fileType,
-          title: remoteDiff.title,
+          title: remoteDiff.title ?? '',
           deviceFolder: remoteDiff.deviceFolder ?? '',
           deviceFolder: remoteDiff.deviceFolder ?? '',
         );
         );
         if (localFileEntries.isEmpty) {
         if (localFileEntries.isEmpty) {
@@ -653,18 +657,18 @@ class RemoteSyncService {
               )
               )
               .reduce(max);
               .reduce(max);
 
 
-          /* todo: In case of iOS, we will miss any asset modification in
+          /* Note: In case of iOS, we will miss any asset modification in
             between of two installation. This is done to avoid fetching assets
             between of two installation. This is done to avoid fetching assets
             from iCloud when modification time could have changed for number of
             from iCloud when modification time could have changed for number of
             reasons. To fix this, we need to identify a way to store version
             reasons. To fix this, we need to identify a way to store version
             for the adjustments or just if the asset has been modified ever.
             for the adjustments or just if the asset has been modified ever.
             https://stackoverflow.com/a/50093266/546896
             https://stackoverflow.com/a/50093266/546896
             */
             */
-          if (maxModificationTime > remoteDiff.modificationTime &&
+          if (maxModificationTime > remoteDiff.modificationTime! &&
               Platform.isAndroid) {
               Platform.isAndroid) {
             localButUpdatedOnDevice++;
             localButUpdatedOnDevice++;
             await FileUpdationDB.instance.insertMultiple(
             await FileUpdationDB.instance.insertMultiple(
-              [remoteDiff.localID],
+              [remoteDiff.localID!],
               FileUpdationDB.modificationTimeUpdated,
               FileUpdationDB.modificationTimeUpdated,
             );
             );
           }
           }
@@ -680,7 +684,7 @@ class RemoteSyncService {
             // setting the generated ID of remoteFile to localFile generatedID
             // setting the generated ID of remoteFile to localFile generatedID
             existingFile = localFileEntries.first;
             existingFile = localFileEntries.first;
             localUploadedFromDevice++;
             localUploadedFromDevice++;
-            alreadyClaimedLocalFilesGenID.add(existingFile.generatedID);
+            alreadyClaimedLocalFilesGenID.add(existingFile.generatedID!);
             remoteDiff.generatedID = existingFile.generatedID;
             remoteDiff.generatedID = existingFile.generatedID;
           }
           }
         }
         }
@@ -716,17 +720,17 @@ class RemoteSyncService {
   }
   }
 
 
   bool _shouldClearCache(File remoteFile, File existingFile) {
   bool _shouldClearCache(File remoteFile, File existingFile) {
-    if (remoteFile.hash != null && existingFile?.hash != null) {
+    if (remoteFile.hash != null && existingFile.hash != null) {
       return remoteFile.hash != existingFile.hash;
       return remoteFile.hash != existingFile.hash;
     }
     }
-    return remoteFile.updationTime != (existingFile?.updationTime ?? 0);
+    return remoteFile.updationTime != (existingFile.updationTime ?? 0);
   }
   }
 
 
   bool _shouldReloadHomeGallery(File remoteFile, File existingFile) {
   bool _shouldReloadHomeGallery(File remoteFile, File existingFile) {
-    int remoteCreationTime = remoteFile.creationTime;
+    int remoteCreationTime = remoteFile.creationTime!;
     if (remoteFile.pubMmdVersion > 0 &&
     if (remoteFile.pubMmdVersion > 0 &&
-        (remoteFile.pubMagicMetadata.editedTime ?? 0) != 0) {
-      remoteCreationTime = remoteFile.pubMagicMetadata.editedTime;
+        (remoteFile.pubMagicMetadata?.editedTime ?? 0) != 0) {
+      remoteCreationTime = remoteFile.pubMagicMetadata!.editedTime!;
     }
     }
     if (remoteCreationTime != existingFile.creationTime) {
     if (remoteCreationTime != existingFile.creationTime) {
       return true;
       return true;
@@ -774,7 +778,7 @@ class RemoteSyncService {
   void _sortByTimeAndType(List<File> file) {
   void _sortByTimeAndType(List<File> file) {
     file.sort((first, second) {
     file.sort((first, second) {
       if (first.fileType == second.fileType) {
       if (first.fileType == second.fileType) {
-        return second.creationTime.compareTo(first.creationTime);
+        return second.creationTime!.compareTo(first.creationTime!);
       } else if (first.fileType == FileType.video) {
       } else if (first.fileType == FileType.video) {
         return 1;
         return 1;
       } else {
       } else {

+ 20 - 21
lib/services/search_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/network.dart';
 import 'package:photos/core/network.dart';
@@ -21,7 +19,7 @@ import 'package:photos/utils/date_time_util.dart';
 import 'package:tuple/tuple.dart';
 import 'package:tuple/tuple.dart';
 
 
 class SearchService {
 class SearchService {
-  Future<List<File>> _cachedFilesFuture;
+  Future<List<File>>? _cachedFilesFuture;
   final _enteDio = Network.instance.enteDio;
   final _enteDio = Network.instance.enteDio;
   final _logger = Logger((SearchService).toString());
   final _logger = Logger((SearchService).toString());
   final _collectionService = CollectionsService.instance;
   final _collectionService = CollectionsService.instance;
@@ -44,12 +42,12 @@ class SearchService {
 
 
   Future<List<File>> _getAllFiles() async {
   Future<List<File>> _getAllFiles() async {
     if (_cachedFilesFuture != null) {
     if (_cachedFilesFuture != null) {
-      return _cachedFilesFuture;
+      return _cachedFilesFuture!;
     }
     }
     _logger.fine("Reading all files from db");
     _logger.fine("Reading all files from db");
     _cachedFilesFuture =
     _cachedFilesFuture =
         FilesDB.instance.getAllFilesFromDB(ignoreCollections());
         FilesDB.instance.getAllFilesFromDB(ignoreCollections());
-    return _cachedFilesFuture;
+    return _cachedFilesFuture!;
   }
   }
 
 
   void clearCache() {
   void clearCache() {
@@ -75,12 +73,13 @@ class SearchService {
 
 
         for (var file in allFiles) {
         for (var file in allFiles) {
           if (_isValidLocation(file.location) &&
           if (_isValidLocation(file.location) &&
-              _isLocationWithinBounds(file.location, locationData)) {
+              _isLocationWithinBounds(file.location!, locationData)) {
             filesInLocation.add(file);
             filesInLocation.add(file);
           }
           }
         }
         }
         filesInLocation.sort(
         filesInLocation.sort(
-          (first, second) => second.creationTime.compareTo(first.creationTime),
+          (first, second) =>
+              second.creationTime!.compareTo(first.creationTime!),
         );
         );
         if (filesInLocation.isNotEmpty) {
         if (filesInLocation.isNotEmpty) {
           searchResults.add(
           searchResults.add(
@@ -116,7 +115,7 @@ class SearchService {
       }
       }
 
 
       if (!c.collection.isHidden() &&
       if (!c.collection.isHidden() &&
-          c.collection.name.toLowerCase().contains(
+          c.collection.name!.toLowerCase().contains(
                 query.toLowerCase(),
                 query.toLowerCase(),
               )) {
               )) {
         collectionSearchResults.add(AlbumSearchResult(c));
         collectionSearchResults.add(AlbumSearchResult(c));
@@ -206,7 +205,7 @@ class SearchService {
     final List<File> captionMatch = <File>[];
     final List<File> captionMatch = <File>[];
     final List<File> displayNameMatch = <File>[];
     final List<File> displayNameMatch = <File>[];
     for (File eachFile in allFiles) {
     for (File eachFile in allFiles) {
-      if (eachFile.caption != null && pattern.hasMatch(eachFile.caption)) {
+      if (eachFile.caption != null && pattern.hasMatch(eachFile.caption!)) {
         captionMatch.add(eachFile);
         captionMatch.add(eachFile);
       }
       }
       if (pattern.hasMatch(eachFile.displayName)) {
       if (pattern.hasMatch(eachFile.displayName)) {
@@ -252,7 +251,7 @@ class SearchService {
         if (!resultMap.containsKey(exnType)) {
         if (!resultMap.containsKey(exnType)) {
           resultMap[exnType] = <File>[];
           resultMap[exnType] = <File>[];
         }
         }
-        resultMap[exnType].add(eachFile);
+        resultMap[exnType]!.add(eachFile);
       }
       }
     }
     }
     for (MapEntry<String, List<File>> entry in resultMap.entries) {
     for (MapEntry<String, List<File>> entry in resultMap.entries) {
@@ -298,7 +297,7 @@ class SearchService {
     for (var potentialDate in potentialDates) {
     for (var potentialDate in potentialDates) {
       final int day = potentialDate.item1;
       final int day = potentialDate.item1;
       final int month = potentialDate.item2.monthNumber;
       final int month = potentialDate.item2.monthNumber;
-      final int year = potentialDate.item3; // nullable
+      final int? year = potentialDate.item3; // nullable
       final matchedFiles =
       final matchedFiles =
           await FilesDB.instance.getFilesCreatedWithinDurations(
           await FilesDB.instance.getFilesCreatedWithinDurations(
         _getDurationsForCalendarDateInEveryYear(day, month, year: year),
         _getDurationsForCalendarDateInEveryYear(day, month, year: year),
@@ -338,7 +337,7 @@ class SearchService {
   List<List<int>> _getDurationsForCalendarDateInEveryYear(
   List<List<int>> _getDurationsForCalendarDateInEveryYear(
     int day,
     int day,
     int month, {
     int month, {
-    int year,
+    int? year,
   }) {
   }) {
     final List<List<int>> durationsOfHolidayInEveryYear = [];
     final List<List<int>> durationsOfHolidayInEveryYear = [];
     final int startYear = year ?? searchStartYear;
     final int startYear = year ?? searchStartYear;
@@ -367,7 +366,7 @@ class SearchService {
     return durationsOfMonthInEveryYear;
     return durationsOfMonthInEveryYear;
   }
   }
 
 
-  bool _isValidLocation(Location location) {
+  bool _isValidLocation(Location? location) {
     return location != null &&
     return location != null &&
         location.latitude != null &&
         location.latitude != null &&
         location.latitude != 0 &&
         location.latitude != 0 &&
@@ -380,14 +379,14 @@ class SearchService {
     LocationDataFromResponse locationData,
     LocationDataFromResponse locationData,
   ) {
   ) {
     //format returned by the api is [lng,lat,lng,lat] where indexes 0 & 1 are southwest and 2 & 3 northeast
     //format returned by the api is [lng,lat,lng,lat] where indexes 0 & 1 are southwest and 2 & 3 northeast
-    return location.longitude > locationData.bbox[0] &&
-        location.latitude > locationData.bbox[1] &&
-        location.longitude < locationData.bbox[2] &&
-        location.latitude < locationData.bbox[3];
+    return location.longitude! > locationData.bbox[0] &&
+        location.latitude! > locationData.bbox[1] &&
+        location.longitude! < locationData.bbox[2] &&
+        location.latitude! < locationData.bbox[3];
   }
   }
 
 
-  List<Tuple3<int, MonthData, int>> _getPossibleEventDate(String query) {
-    final List<Tuple3<int, MonthData, int>> possibleEvents = [];
+  List<Tuple3<int, MonthData, int?>> _getPossibleEventDate(String query) {
+    final List<Tuple3<int, MonthData, int?>> possibleEvents = [];
     if (query.trim().isEmpty) {
     if (query.trim().isEmpty) {
       return possibleEvents;
       return possibleEvents;
     }
     }
@@ -402,13 +401,13 @@ class SearchService {
       return possibleEvents;
       return possibleEvents;
     }
     }
 
 
-    final int day = int.tryParse(result[0]);
+    final int? day = int.tryParse(result[0]);
     if (day == null || day < 1 || day > 31) {
     if (day == null || day < 1 || day > 31) {
       return possibleEvents;
       return possibleEvents;
     }
     }
     final List<MonthData> potentialMonth =
     final List<MonthData> potentialMonth =
         resultCount > 1 ? _getMatchingMonths(result[1]) : allMonths;
         resultCount > 1 ? _getMatchingMonths(result[1]) : allMonths;
-    final int parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null;
+    final int? parsedYear = resultCount >= 3 ? int.tryParse(result[2]) : null;
     final List<int> matchingYears = [];
     final List<int> matchingYears = [];
     if (parsedYear != null) {
     if (parsedYear != null) {
       bool foundMatch = false;
       bool foundMatch = false;

+ 14 - 14
lib/services/sync_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
@@ -33,9 +31,9 @@ class SyncService {
   final _enteDio = Network.instance.enteDio;
   final _enteDio = Network.instance.enteDio;
   final _uploader = FileUploader.instance;
   final _uploader = FileUploader.instance;
   bool _syncStopRequested = false;
   bool _syncStopRequested = false;
-  Completer<bool> _existingSync;
-  SharedPreferences _prefs;
-  SyncStatusUpdate _lastSyncStatusEvent;
+  Completer<bool>? _existingSync;
+  late SharedPreferences _prefs;
+  SyncStatusUpdate? _lastSyncStatusEvent;
 
 
   static const kLastStorageLimitExceededNotificationPushTime =
   static const kLastStorageLimitExceededNotificationPushTime =
       "last_storage_limit_exceeded_notification_push_time";
       "last_storage_limit_exceeded_notification_push_time";
@@ -70,24 +68,26 @@ class SyncService {
     }
     }
   }
   }
 
 
+  // Note: Do not use this future for anything except log out.
+  // This is prone to bugs due to any potential race conditions
   Future<bool> existingSync() async {
   Future<bool> existingSync() async {
-    return _existingSync.future;
+    return _existingSync?.future ?? Future.value(true);
   }
   }
 
 
   Future<bool> sync() async {
   Future<bool> sync() async {
     _syncStopRequested = false;
     _syncStopRequested = false;
     if (_existingSync != null) {
     if (_existingSync != null) {
       _logger.warning("Sync already in progress, skipping.");
       _logger.warning("Sync already in progress, skipping.");
-      return _existingSync.future;
+      return _existingSync!.future;
     }
     }
     _existingSync = Completer<bool>();
     _existingSync = Completer<bool>();
     bool successful = false;
     bool successful = false;
     try {
     try {
       await _doSync();
       await _doSync();
       if (_lastSyncStatusEvent != null &&
       if (_lastSyncStatusEvent != null &&
-          _lastSyncStatusEvent.status !=
+          _lastSyncStatusEvent!.status !=
               SyncStatus.completedFirstGalleryImport &&
               SyncStatus.completedFirstGalleryImport &&
-          _lastSyncStatusEvent.status != SyncStatus.completedBackup) {
+          _lastSyncStatusEvent!.status != SyncStatus.completedBackup) {
         Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
         Bus.instance.fire(SyncStatusUpdate(SyncStatus.completedBackup));
       }
       }
       successful = true;
       successful = true;
@@ -139,7 +139,7 @@ class SyncService {
       Bus.instance.fire(SyncStatusUpdate(SyncStatus.error));
       Bus.instance.fire(SyncStatusUpdate(SyncStatus.error));
       rethrow;
       rethrow;
     } finally {
     } finally {
-      _existingSync.complete(successful);
+      _existingSync?.complete(successful);
       _existingSync = null;
       _existingSync = null;
       _lastSyncStatusEvent = null;
       _lastSyncStatusEvent = null;
       _logger.info("Syncing completed");
       _logger.info("Syncing completed");
@@ -160,7 +160,7 @@ class SyncService {
     return _existingSync != null;
     return _existingSync != null;
   }
   }
 
 
-  SyncStatusUpdate getLastSyncStatusEvent() {
+  SyncStatusUpdate? getLastSyncStatusEvent() {
     return _lastSyncStatusEvent;
     return _lastSyncStatusEvent;
   }
   }
 
 
@@ -198,7 +198,7 @@ class SyncService {
     );
     );
   }
   }
 
 
-  Future<void> deleteFilesOnServer(List<int> fileIDs) async {
+  Future<Response> deleteFilesOnServer(List<int> fileIDs) async {
     return await _enteDio.post(
     return await _enteDio.post(
       "/files/delete",
       "/files/delete",
       data: {
       data: {
@@ -207,14 +207,14 @@ class SyncService {
     );
     );
   }
   }
 
 
-  Future<BackupStatus> getBackupStatus({String pathID}) async {
+  Future<BackupStatus> getBackupStatus({String? pathID}) async {
     BackedUpFileIDs ids;
     BackedUpFileIDs ids;
     if (pathID == null) {
     if (pathID == null) {
       ids = await FilesDB.instance.getBackedUpIDs();
       ids = await FilesDB.instance.getBackedUpIDs();
     } else {
     } else {
       ids = await FilesDB.instance.getBackedUpForDeviceCollection(
       ids = await FilesDB.instance.getBackedUpForDeviceCollection(
         pathID,
         pathID,
-        Configuration.instance.getUserID(),
+        Configuration.instance.getUserID()!,
       );
       );
     }
     }
     final size = await _getFileSize(ids.uploadedIDs);
     final size = await _getFileSize(ids.uploadedIDs);

+ 4 - 6
lib/services/trash_sync_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:dio/dio.dart';
 import 'package:dio/dio.dart';
@@ -25,7 +23,7 @@ class TrashSyncService {
   final _diffFetcher = TrashDiffFetcher();
   final _diffFetcher = TrashDiffFetcher();
   final _trashDB = TrashDB.instance;
   final _trashDB = TrashDB.instance;
   static const kLastTrashSyncTime = "last_trash_sync_time";
   static const kLastTrashSyncTime = "last_trash_sync_time";
-  SharedPreferences _prefs;
+  late SharedPreferences _prefs;
 
 
   TrashSyncService._privateConstructor();
   TrashSyncService._privateConstructor();
 
 
@@ -55,7 +53,7 @@ class TrashSyncService {
     if (diff.restoredFiles.isNotEmpty) {
     if (diff.restoredFiles.isNotEmpty) {
       _logger.fine("discard ${diff.restoredFiles.length} restored items");
       _logger.fine("discard ${diff.restoredFiles.length} restored items");
       final itemsDeleted = await _trashDB
       final itemsDeleted = await _trashDB
-          .delete(diff.restoredFiles.map((e) => e.uploadedFileID).toList());
+          .delete(diff.restoredFiles.map((e) => e.uploadedFileID!).toList());
       isLocalTrashUpdated = isLocalTrashUpdated || itemsDeleted > 0;
       isLocalTrashUpdated = isLocalTrashUpdated || itemsDeleted > 0;
     }
     }
 
 
@@ -97,7 +95,7 @@ class TrashSyncService {
     }
     }
   }
   }
 
 
-  Future<void> _setSyncTime(int time) async {
+  Future<bool> _setSyncTime(int time) async {
     return _prefs.setInt(kLastTrashSyncTime, time);
     return _prefs.setInt(kLastTrashSyncTime, time);
   }
   }
 
 
@@ -136,7 +134,7 @@ class TrashSyncService {
 
 
   Future<void> deleteFromTrash(List<File> files) async {
   Future<void> deleteFromTrash(List<File> files) async {
     final params = <String, dynamic>{};
     final params = <String, dynamic>{};
-    final uniqueFileIds = files.map((e) => e.uploadedFileID).toSet().toList();
+    final uniqueFileIds = files.map((e) => e.uploadedFileID!).toSet().toList();
     final batchedFileIDs = uniqueFileIds.chunks(batchSize);
     final batchedFileIDs = uniqueFileIds.chunks(batchSize);
     for (final batch in batchedFileIDs) {
     for (final batch in batchedFileIDs) {
       params["fileIDs"] = [];
       params["fileIDs"] = [];

+ 6 - 8
lib/services/update_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:io';
 import 'dart:io';
 
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
@@ -19,10 +17,10 @@ class UpdateService {
   static const changeLogVersionKey = "update_change_log_key";
   static const changeLogVersionKey = "update_change_log_key";
   static const currentChangeLogVersion = 3;
   static const currentChangeLogVersion = 3;
 
 
-  LatestVersionInfo _latestVersion;
+  LatestVersionInfo? _latestVersion;
   final _logger = Logger("UpdateService");
   final _logger = Logger("UpdateService");
-  PackageInfo _packageInfo;
-  SharedPreferences _prefs;
+  late PackageInfo _packageInfo;
+  late SharedPreferences _prefs;
 
 
   Future<void> init() async {
   Future<void> init() async {
     _packageInfo = await PackageInfo.fromPlatform();
     _packageInfo = await PackageInfo.fromPlatform();
@@ -51,7 +49,7 @@ class UpdateService {
     try {
     try {
       _latestVersion = await _getLatestVersionInfo();
       _latestVersion = await _getLatestVersionInfo();
       final currentVersionCode = int.parse(_packageInfo.buildNumber);
       final currentVersionCode = int.parse(_packageInfo.buildNumber);
-      return currentVersionCode < _latestVersion.code;
+      return currentVersionCode < _latestVersion!.code;
     } catch (e) {
     } catch (e) {
       _logger.severe(e);
       _logger.severe(e);
       return false;
       return false;
@@ -71,7 +69,7 @@ class UpdateService {
     }
     }
   }
   }
 
 
-  LatestVersionInfo getLatestVersionInfo() {
+  LatestVersionInfo? getLatestVersionInfo() {
     return _latestVersion;
     return _latestVersion;
   }
   }
 
 
@@ -87,7 +85,7 @@ class UpdateService {
         (now - lastNotificationShownTime) > (3 * microSecondsInDay);
         (now - lastNotificationShownTime) > (3 * microSecondsInDay);
     if (shouldUpdate &&
     if (shouldUpdate &&
         hasBeen3DaysSinceLastNotification &&
         hasBeen3DaysSinceLastNotification &&
-        _latestVersion.shouldNotify) {
+        _latestVersion!.shouldNotify) {
       NotificationService.instance.showNotification(
       NotificationService.instance.showNotification(
         "Update available",
         "Update available",
         "Click to install our best version yet",
         "Click to install our best version yet",

+ 36 - 37
lib/services/user_service.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:typed_data';
 import 'dart:typed_data';
 
 
@@ -43,9 +41,9 @@ class UserService {
   final _enteDio = Network.instance.enteDio;
   final _enteDio = Network.instance.enteDio;
   final _logger = Logger((UserService).toString());
   final _logger = Logger((UserService).toString());
   final _config = Configuration.instance;
   final _config = Configuration.instance;
-  SharedPreferences _preferences;
+  late SharedPreferences _preferences;
 
 
-  ValueNotifier<String> emailValueNotifier;
+  late ValueNotifier<String?> emailValueNotifier;
 
 
   UserService._privateConstructor();
   UserService._privateConstructor();
 
 
@@ -53,7 +51,7 @@ class UserService {
 
 
   Future<void> init() async {
   Future<void> init() async {
     emailValueNotifier =
     emailValueNotifier =
-        ValueNotifier<String>(Configuration.instance.getEmail());
+        ValueNotifier<String?>(Configuration.instance.getEmail());
     _preferences = await SharedPreferences.getInstance();
     _preferences = await SharedPreferences.getInstance();
     if (Configuration.instance.isLoggedIn()) {
     if (Configuration.instance.isLoggedIn()) {
       // add artificial delay in refreshing 2FA status
       // add artificial delay in refreshing 2FA status
@@ -81,7 +79,7 @@ class UserService {
         data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
         data: {"email": email, "purpose": isChangeEmail ? "change" : ""},
       );
       );
       await dialog.hide();
       await dialog.hide();
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         unawaited(
         unawaited(
           Navigator.of(context).push(
           Navigator.of(context).push(
             MaterialPageRoute(
             MaterialPageRoute(
@@ -101,7 +99,7 @@ class UserService {
     } on DioError catch (e) {
     } on DioError catch (e) {
       await dialog.hide();
       await dialog.hide();
       _logger.info(e);
       _logger.info(e);
-      if (e.response != null && e.response.statusCode == 403) {
+      if (e.response != null && e.response!.statusCode == 403) {
         unawaited(
         unawaited(
           showErrorDialog(
           showErrorDialog(
             context,
             context,
@@ -119,7 +117,7 @@ class UserService {
     }
     }
   }
   }
 
 
-  Future<String> getPublicKey(String email) async {
+  Future<String?> getPublicKey(String email) async {
     try {
     try {
       final response = await _enteDio.get(
       final response = await _enteDio.get(
         "/users/public-key",
         "/users/public-key",
@@ -134,9 +132,9 @@ class UserService {
     }
     }
   }
   }
 
 
-  UserDetails getCachedUserDetails() {
+  UserDetails? getCachedUserDetails() {
     if (_preferences.containsKey(keyUserDetails)) {
     if (_preferences.containsKey(keyUserDetails)) {
-      return UserDetails.fromJson(_preferences.getString(keyUserDetails));
+      return UserDetails.fromJson(_preferences.getString(keyUserDetails)!);
     }
     }
     return null;
     return null;
   }
   }
@@ -203,7 +201,7 @@ class UserService {
     await dialog.show();
     await dialog.show();
     try {
     try {
       final response = await _enteDio.post("/users/logout");
       final response = await _enteDio.post("/users/logout");
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         await Configuration.instance.logout();
         await Configuration.instance.logout();
         await dialog.hide();
         await dialog.hide();
         Navigator.of(context).popUntil((route) => route.isFirst);
         Navigator.of(context).popUntil((route) => route.isFirst);
@@ -217,14 +215,14 @@ class UserService {
     }
     }
   }
   }
 
 
-  Future<DeleteChallengeResponse> getDeleteChallenge(
+  Future<DeleteChallengeResponse?> getDeleteChallenge(
     BuildContext context,
     BuildContext context,
   ) async {
   ) async {
     final dialog = createProgressDialog(context, "Please wait...");
     final dialog = createProgressDialog(context, "Please wait...");
     await dialog.show();
     await dialog.show();
     try {
     try {
       final response = await _enteDio.get("/users/delete-challenge");
       final response = await _enteDio.get("/users/delete-challenge");
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         // clear data
         // clear data
         await dialog.hide();
         await dialog.hide();
         return DeleteChallengeResponse(
         return DeleteChallengeResponse(
@@ -253,7 +251,7 @@ class UserService {
           "challenge": challengeResponse,
           "challenge": challengeResponse,
         },
         },
       );
       );
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         // clear data
         // clear data
         await Configuration.instance.logout();
         await Configuration.instance.logout();
       } else {
       } else {
@@ -277,10 +275,11 @@ class UserService {
         },
         },
       );
       );
       await dialog.hide();
       await dialog.hide();
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         Widget page;
         Widget page;
         final String twoFASessionID = response.data["twoFactorSessionID"];
         final String twoFASessionID = response.data["twoFactorSessionID"];
-        if (twoFASessionID != null && twoFASessionID.isNotEmpty) {
+        if (twoFASessionID.isNotEmpty) {
+          setTwoFactor(value: true);
           page = TwoFactorAuthenticationPage(twoFASessionID);
           page = TwoFactorAuthenticationPage(twoFASessionID);
         } else {
         } else {
           await _saveConfiguration(response);
           await _saveConfiguration(response);
@@ -305,7 +304,7 @@ class UserService {
     } on DioError catch (e) {
     } on DioError catch (e) {
       _logger.info(e);
       _logger.info(e);
       await dialog.hide();
       await dialog.hide();
-      if (e.response != null && e.response.statusCode == 410) {
+      if (e.response != null && e.response!.statusCode == 410) {
         await showErrorDialog(
         await showErrorDialog(
           context,
           context,
           "Oops",
           "Oops",
@@ -328,7 +327,7 @@ class UserService {
 
 
   Future<void> setEmail(String email) async {
   Future<void> setEmail(String email) async {
     await _config.setEmail(email);
     await _config.setEmail(email);
-    emailValueNotifier.value = email ?? "";
+    emailValueNotifier.value = email;
   }
   }
 
 
   Future<void> changeEmail(
   Future<void> changeEmail(
@@ -347,7 +346,7 @@ class UserService {
         },
         },
       );
       );
       await dialog.hide();
       await dialog.hide();
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         showShortToast(context, "Email changed to " + email);
         showShortToast(context, "Email changed to " + email);
         await setEmail(email);
         await setEmail(email);
         Navigator.of(context).popUntil((route) => route.isFirst);
         Navigator.of(context).popUntil((route) => route.isFirst);
@@ -357,7 +356,7 @@ class UserService {
       showErrorDialog(context, "Oops", "Verification failed, please try again");
       showErrorDialog(context, "Oops", "Verification failed, please try again");
     } on DioError catch (e) {
     } on DioError catch (e) {
       await dialog.hide();
       await dialog.hide();
-      if (e.response != null && e.response.statusCode == 403) {
+      if (e.response != null && e.response!.statusCode == 403) {
         showErrorDialog(context, "Oops", "This email is already in use");
         showErrorDialog(context, "Oops", "This email is already in use");
       } else {
       } else {
         showErrorDialog(
         showErrorDialog(
@@ -417,8 +416,8 @@ class UserService {
       final setRecoveryKeyRequest = SetRecoveryKeyRequest(
       final setRecoveryKeyRequest = SetRecoveryKeyRequest(
         keyAttributes.masterKeyEncryptedWithRecoveryKey,
         keyAttributes.masterKeyEncryptedWithRecoveryKey,
         keyAttributes.masterKeyDecryptionNonce,
         keyAttributes.masterKeyDecryptionNonce,
-        keyAttributes.recoveryKeyEncryptedWithMasterKey,
-        keyAttributes.recoveryKeyDecryptionNonce,
+        keyAttributes.recoveryKeyEncryptedWithMasterKey!,
+        keyAttributes.recoveryKeyDecryptionNonce!,
       );
       );
       await _enteDio.put(
       await _enteDio.put(
         "/users/recovery-key",
         "/users/recovery-key",
@@ -447,7 +446,7 @@ class UserService {
         },
         },
       );
       );
       await dialog.hide();
       await dialog.hide();
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         showShortToast(context, "Authentication successful!");
         showShortToast(context, "Authentication successful!");
         await _saveConfiguration(response);
         await _saveConfiguration(response);
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
@@ -462,7 +461,7 @@ class UserService {
     } on DioError catch (e) {
     } on DioError catch (e) {
       await dialog.hide();
       await dialog.hide();
       _logger.severe(e);
       _logger.severe(e);
-      if (e.response != null && e.response.statusCode == 404) {
+      if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, "Session expired");
         showToast(context, "Session expired");
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
           MaterialPageRoute(
           MaterialPageRoute(
@@ -500,7 +499,7 @@ class UserService {
           "sessionID": sessionID,
           "sessionID": sessionID,
         },
         },
       );
       );
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
           MaterialPageRoute(
           MaterialPageRoute(
             builder: (BuildContext context) {
             builder: (BuildContext context) {
@@ -516,7 +515,7 @@ class UserService {
       }
       }
     } on DioError catch (e) {
     } on DioError catch (e) {
       _logger.severe(e);
       _logger.severe(e);
-      if (e.response != null && e.response.statusCode == 404) {
+      if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, "Session expired");
         showToast(context, "Session expired");
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
           MaterialPageRoute(
           MaterialPageRoute(
@@ -588,7 +587,7 @@ class UserService {
           "secret": secret,
           "secret": secret,
         },
         },
       );
       );
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         showShortToast(context, "Two-factor authentication successfully reset");
         showShortToast(context, "Two-factor authentication successfully reset");
         await _saveConfiguration(response);
         await _saveConfiguration(response);
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
@@ -602,7 +601,7 @@ class UserService {
       }
       }
     } on DioError catch (e) {
     } on DioError catch (e) {
       _logger.severe(e);
       _logger.severe(e);
-      if (e.response != null && e.response.statusCode == 404) {
+      if (e.response != null && e.response!.statusCode == 404) {
         showToast(context, "Session expired");
         showToast(context, "Session expired");
         Navigator.of(context).pushAndRemoveUntil(
         Navigator.of(context).pushAndRemoveUntil(
           MaterialPageRoute(
           MaterialPageRoute(
@@ -677,9 +676,9 @@ class UserService {
         data: {
         data: {
           "code": code,
           "code": code,
           "encryptedTwoFactorSecret":
           "encryptedTwoFactorSecret":
-              Sodium.bin2base64(encryptionResult.encryptedData),
+              Sodium.bin2base64(encryptionResult.encryptedData as Uint8List),
           "twoFactorSecretDecryptionNonce":
           "twoFactorSecretDecryptionNonce":
-              Sodium.bin2base64(encryptionResult.nonce),
+              Sodium.bin2base64(encryptionResult.nonce as Uint8List),
         },
         },
       );
       );
       await dialog.hide();
       await dialog.hide();
@@ -690,7 +689,7 @@ class UserService {
       await dialog.hide();
       await dialog.hide();
       _logger.severe(e, s);
       _logger.severe(e, s);
       if (e is DioError) {
       if (e is DioError) {
-        if (e.response != null && e.response.statusCode == 401) {
+        if (e.response != null && e.response!.statusCode == 401) {
           showErrorDialog(
           showErrorDialog(
             context,
             context,
             "Incorrect code",
             "Incorrect code",
@@ -747,8 +746,8 @@ class UserService {
   }
   }
 
 
   Future<Uint8List> getOrCreateRecoveryKey(BuildContext context) async {
   Future<Uint8List> getOrCreateRecoveryKey(BuildContext context) async {
-    final encryptedRecoveryKey =
-        _config.getKeyAttributes().recoveryKeyEncryptedWithMasterKey;
+    final String? encryptedRecoveryKey =
+        _config.getKeyAttributes()!.recoveryKeyEncryptedWithMasterKey;
     if (encryptedRecoveryKey == null || encryptedRecoveryKey.isEmpty) {
     if (encryptedRecoveryKey == null || encryptedRecoveryKey.isEmpty) {
       final dialog = createProgressDialog(context, "Please wait...");
       final dialog = createProgressDialog(context, "Please wait...");
       await dialog.show();
       await dialog.show();
@@ -766,10 +765,10 @@ class UserService {
     return recoveryKey;
     return recoveryKey;
   }
   }
 
 
-  Future<String> getPaymentToken() async {
+  Future<String?> getPaymentToken() async {
     try {
     try {
       final response = await _enteDio.get("/users/payment-token");
       final response = await _enteDio.get("/users/payment-token");
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         return response.data["paymentToken"];
         return response.data["paymentToken"];
       } else {
       } else {
         throw Exception("non 200 ok response");
         throw Exception("non 200 ok response");
@@ -783,7 +782,7 @@ class UserService {
   Future<String> getFamiliesToken() async {
   Future<String> getFamiliesToken() async {
     try {
     try {
       final response = await _enteDio.get("/users/families-token");
       final response = await _enteDio.get("/users/families-token");
-      if (response != null && response.statusCode == 200) {
+      if (response.statusCode == 200) {
         return response.data["familiesToken"];
         return response.data["familiesToken"];
       } else {
       } else {
         throw Exception("non 200 ok response");
         throw Exception("non 200 ok response");
@@ -818,6 +817,6 @@ class UserService {
   }
   }
 
 
   bool hasEnabledTwoFactor() {
   bool hasEnabledTwoFactor() {
-    return _preferences.getBool(keyHasEnabledTwoFactor);
+    return _preferences.getBool(keyHasEnabledTwoFactor) ?? false;
   }
   }
 }
 }

+ 1 - 1
lib/ui/account/change_email_dialog.dart

@@ -71,7 +71,7 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
               );
               );
               return;
               return;
             }
             }
-            UserService.instance.sendOtt(context, _email, isChangeEmail: true);
+            UserService.instance.sendOtt(context, _email!, isChangeEmail: true);
           },
           },
         ),
         ),
       ],
       ],

+ 5 - 7
lib/ui/account/delete_account_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -17,7 +15,7 @@ import 'package:photos/utils/email_util.dart';
 
 
 class DeleteAccountPage extends StatelessWidget {
 class DeleteAccountPage extends StatelessWidget {
   const DeleteAccountPage({
   const DeleteAccountPage({
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -53,7 +51,7 @@ class DeleteAccountPage extends StatelessWidget {
                   "We'll be sorry to see you go. Are you facing some issue?",
                   "We'll be sorry to see you go. Are you facing some issue?",
                   style: Theme.of(context)
                   style: Theme.of(context)
                       .textTheme
                       .textTheme
-                      .subtitle1
+                      .subtitle1!
                       .copyWith(color: colorScheme.textMuted),
                       .copyWith(color: colorScheme.textMuted),
                 ),
                 ),
               ),
               ),
@@ -75,7 +73,7 @@ class DeleteAccountPage extends StatelessWidget {
                   ],
                   ],
                   style: Theme.of(context)
                   style: Theme.of(context)
                       .textTheme
                       .textTheme
-                      .subtitle1
+                      .subtitle1!
                       .copyWith(color: colorScheme.textMuted),
                       .copyWith(color: colorScheme.textMuted),
                 ),
                 ),
               ),
               ),
@@ -145,9 +143,9 @@ class DeleteAccountPage extends StatelessWidget {
           final decryptChallenge = CryptoUtil.openSealSync(
           final decryptChallenge = CryptoUtil.openSealSync(
             Sodium.base642bin(response.encryptedChallenge),
             Sodium.base642bin(response.encryptedChallenge),
             Sodium.base642bin(
             Sodium.base642bin(
-              Configuration.instance.getKeyAttributes().publicKey,
+              Configuration.instance.getKeyAttributes()!.publicKey,
             ),
             ),
-            Configuration.instance.getSecretKey(),
+            Configuration.instance.getSecretKey()!,
           );
           );
           final challengeResponseStr = utf8.decode(decryptChallenge);
           final challengeResponseStr = utf8.decode(decryptChallenge);
           await UserService.instance
           await UserService.instance

+ 15 - 17
lib/ui/account/email_entry_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:email_validator/email_validator.dart';
 import 'package:email_validator/email_validator.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -13,7 +11,7 @@ import 'package:photos/ui/common/web_page.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 
 
 class EmailEntryPage extends StatefulWidget {
 class EmailEntryPage extends StatefulWidget {
-  const EmailEntryPage({Key key}) : super(key: key);
+  const EmailEntryPage({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<EmailEntryPage> createState() => _EmailEntryPageState();
   State<EmailEntryPage> createState() => _EmailEntryPageState();
@@ -28,8 +26,8 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
   final _passwordController2 = TextEditingController();
   final _passwordController2 = TextEditingController();
   final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
   final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
 
 
-  String _email;
-  String _password;
+  String? _email;
+  String? _password;
   String _cnfPassword = '';
   String _cnfPassword = '';
   double _passwordStrength = 0.0;
   double _passwordStrength = 0.0;
   bool _emailIsValid = false;
   bool _emailIsValid = false;
@@ -65,7 +63,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
 
 
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -104,9 +102,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
         buttonText: 'Create account',
         buttonText: 'Create account',
         onPressedFunction: () {
         onPressedFunction: () {
           _config.setVolatilePassword(_passwordController1.text);
           _config.setVolatilePassword(_passwordController1.text);
-          UserService.instance.setEmail(_email);
+          UserService.instance.setEmail(_email!);
           UserService.instance
           UserService.instance
-              .sendOtt(context, _email, isCreateAccountScreen: true);
+              .sendOtt(context, _email!, isCreateAccountScreen: true);
           FocusScope.of(context).unfocus();
           FocusScope.of(context).unfocus();
         },
         },
       ),
       ),
@@ -162,7 +160,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                               size: 20,
                               size: 20,
                               color: Theme.of(context)
                               color: Theme.of(context)
                                   .inputDecorationTheme
                                   .inputDecorationTheme
-                                  .focusedBorder
+                                  .focusedBorder!
                                   .borderSide
                                   .borderSide
                                   .color,
                                   .color,
                             )
                             )
@@ -170,9 +168,9 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                     ),
                     ),
                     onChanged: (value) {
                     onChanged: (value) {
                       _email = value.trim();
                       _email = value.trim();
-                      if (_emailIsValid != EmailValidator.validate(_email)) {
+                      if (_emailIsValid != EmailValidator.validate(_email!)) {
                         setState(() {
                         setState(() {
-                          _emailIsValid = EmailValidator.validate(_email);
+                          _emailIsValid = EmailValidator.validate(_email!);
                         });
                         });
                       }
                       }
                     },
                     },
@@ -220,7 +218,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                                   Icons.check,
                                   Icons.check,
                                   color: Theme.of(context)
                                   color: Theme.of(context)
                                       .inputDecorationTheme
                                       .inputDecorationTheme
-                                      .focusedBorder
+                                      .focusedBorder!
                                       .borderSide
                                       .borderSide
                                       .color,
                                       .color,
                                 )
                                 )
@@ -287,7 +285,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                                   Icons.check,
                                   Icons.check,
                                   color: Theme.of(context)
                                   color: Theme.of(context)
                                       .inputDecorationTheme
                                       .inputDecorationTheme
-                                      .focusedBorder
+                                      .focusedBorder!
                                       .borderSide
                                       .borderSide
                                       .color,
                                       .color,
                                 )
                                 )
@@ -363,7 +361,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
             side: CheckboxTheme.of(context).side,
             side: CheckboxTheme.of(context).side,
             onChanged: (value) {
             onChanged: (value) {
               setState(() {
               setState(() {
-                _hasAgreedToTOS = value;
+                _hasAgreedToTOS = value!;
               });
               });
             },
             },
           ),
           ),
@@ -416,7 +414,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                 ],
                 ],
                 style: Theme.of(context)
                 style: Theme.of(context)
                     .textTheme
                     .textTheme
-                    .subtitle1
+                    .subtitle1!
                     .copyWith(fontSize: 12),
                     .copyWith(fontSize: 12),
               ),
               ),
               textAlign: TextAlign.left,
               textAlign: TextAlign.left,
@@ -442,7 +440,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
             side: CheckboxTheme.of(context).side,
             side: CheckboxTheme.of(context).side,
             onChanged: (value) {
             onChanged: (value) {
               setState(() {
               setState(() {
-                _hasAgreedToE2E = value;
+                _hasAgreedToE2E = value!;
               });
               });
             },
             },
           ),
           ),
@@ -477,7 +475,7 @@ class _EmailEntryPageState extends State<EmailEntryPage> {
                 ],
                 ],
                 style: Theme.of(context)
                 style: Theme.of(context)
                     .textTheme
                     .textTheme
-                    .subtitle1
+                    .subtitle1!
                     .copyWith(fontSize: 12),
                     .copyWith(fontSize: 12),
               ),
               ),
               textAlign: TextAlign.left,
               textAlign: TextAlign.left,

+ 10 - 10
lib/ui/account/login_page.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:email_validator/email_validator.dart';
 import 'package:email_validator/email_validator.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/gestures.dart';
@@ -9,7 +9,7 @@ import 'package:photos/ui/common/dynamic_fab.dart';
 import 'package:photos/ui/common/web_page.dart';
 import 'package:photos/ui/common/web_page.dart';
 
 
 class LoginPage extends StatefulWidget {
 class LoginPage extends StatefulWidget {
-  const LoginPage({Key key}) : super(key: key);
+  const LoginPage({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<LoginPage> createState() => _LoginPageState();
   State<LoginPage> createState() => _LoginPageState();
@@ -18,8 +18,8 @@ class LoginPage extends StatefulWidget {
 class _LoginPageState extends State<LoginPage> {
 class _LoginPageState extends State<LoginPage> {
   final _config = Configuration.instance;
   final _config = Configuration.instance;
   bool _emailIsValid = false;
   bool _emailIsValid = false;
-  String _email;
-  Color _emailInputFieldColor;
+  String? _email;
+  Color? _emailInputFieldColor;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -31,7 +31,7 @@ class _LoginPageState extends State<LoginPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
 
 
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -57,9 +57,9 @@ class _LoginPageState extends State<LoginPage> {
         isFormValid: _emailIsValid,
         isFormValid: _emailIsValid,
         buttonText: 'Log in',
         buttonText: 'Log in',
         onPressedFunction: () {
         onPressedFunction: () {
-          UserService.instance.setEmail(_email);
+          UserService.instance.setEmail(_email!);
           UserService.instance
           UserService.instance
-              .sendOtt(context, _email, isCreateAccountScreen: false);
+              .sendOtt(context, _email!, isCreateAccountScreen: false);
           FocusScope.of(context).unfocus();
           FocusScope.of(context).unfocus();
         },
         },
       ),
       ),
@@ -105,7 +105,7 @@ class _LoginPageState extends State<LoginPage> {
                               size: 20,
                               size: 20,
                               color: Theme.of(context)
                               color: Theme.of(context)
                                   .inputDecorationTheme
                                   .inputDecorationTheme
-                                  .focusedBorder
+                                  .focusedBorder!
                                   .borderSide
                                   .borderSide
                                   .color,
                                   .color,
                             )
                             )
@@ -114,7 +114,7 @@ class _LoginPageState extends State<LoginPage> {
                     onChanged: (value) {
                     onChanged: (value) {
                       setState(() {
                       setState(() {
                         _email = value.trim();
                         _email = value.trim();
-                        _emailIsValid = EmailValidator.validate(_email);
+                        _emailIsValid = EmailValidator.validate(_email!);
                         if (_emailIsValid) {
                         if (_emailIsValid) {
                           _emailInputFieldColor =
                           _emailInputFieldColor =
                               const Color.fromRGBO(45, 194, 98, 0.2);
                               const Color.fromRGBO(45, 194, 98, 0.2);
@@ -145,7 +145,7 @@ class _LoginPageState extends State<LoginPage> {
                           text: TextSpan(
                           text: TextSpan(
                             style: Theme.of(context)
                             style: Theme.of(context)
                                 .textTheme
                                 .textTheme
-                                .subtitle1
+                                .subtitle1!
                                 .copyWith(fontSize: 12),
                                 .copyWith(fontSize: 12),
                             children: [
                             children: [
                               const TextSpan(
                               const TextSpan(

+ 6 - 9
lib/ui/account/ott_verification_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 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/services/user_service.dart';
 import 'package:photos/services/user_service.dart';
@@ -15,7 +13,7 @@ class OTTVerificationPage extends StatefulWidget {
     this.email, {
     this.email, {
     this.isChangeEmail = false,
     this.isChangeEmail = false,
     this.isCreateAccountScreen = false,
     this.isCreateAccountScreen = false,
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -29,7 +27,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
 
 
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -65,8 +63,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
       body: _getBody(),
       body: _getBody(),
       floatingActionButton: DynamicFAB(
       floatingActionButton: DynamicFAB(
         isKeypadOpen: isKeypadOpen,
         isKeypadOpen: isKeypadOpen,
-        isFormValid: !(_verificationCodeController.text == null ||
-            _verificationCodeController.text.isEmpty),
+        isFormValid: _verificationCodeController.text.isNotEmpty,
         buttonText: 'Verify',
         buttonText: 'Verify',
         onPressedFunction: () {
         onPressedFunction: () {
           if (widget.isChangeEmail) {
           if (widget.isChangeEmail) {
@@ -114,7 +111,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
                             text: TextSpan(
                             text: TextSpan(
                               style: Theme.of(context)
                               style: Theme.of(context)
                                   .textTheme
                                   .textTheme
-                                  .subtitle1
+                                  .subtitle1!
                                   .copyWith(fontSize: 14),
                                   .copyWith(fontSize: 14),
                               children: [
                               children: [
                                 const TextSpan(text: "We've sent a mail to "),
                                 const TextSpan(text: "We've sent a mail to "),
@@ -134,7 +131,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
                           'Please check your inbox (and spam) to complete verification',
                           'Please check your inbox (and spam) to complete verification',
                           style: Theme.of(context)
                           style: Theme.of(context)
                               .textTheme
                               .textTheme
-                              .subtitle1
+                              .subtitle1!
                               .copyWith(fontSize: 14),
                               .copyWith(fontSize: 14),
                         ),
                         ),
                       ],
                       ],
@@ -187,7 +184,7 @@ class _OTTVerificationPageState extends State<OTTVerificationPage> {
                     },
                     },
                     child: Text(
                     child: Text(
                       "Resend email",
                       "Resend email",
-                      style: Theme.of(context).textTheme.subtitle1.copyWith(
+                      style: Theme.of(context).textTheme.subtitle1!.copyWith(
                             fontSize: 14,
                             fontSize: 14,
                             decoration: TextDecoration.underline,
                             decoration: TextDecoration.underline,
                           ),
                           ),

+ 15 - 17
lib/ui/account/password_entry_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -26,7 +24,7 @@ enum PasswordEntryMode {
 class PasswordEntryPage extends StatefulWidget {
 class PasswordEntryPage extends StatefulWidget {
   final PasswordEntryMode mode;
   final PasswordEntryMode mode;
 
 
-  const PasswordEntryPage({this.mode = PasswordEntryMode.set, Key key})
+  const PasswordEntryPage({this.mode = PasswordEntryMode.set, Key? key})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override
@@ -41,7 +39,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
   final _passwordController1 = TextEditingController(),
   final _passwordController1 = TextEditingController(),
       _passwordController2 = TextEditingController();
       _passwordController2 = TextEditingController();
   final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
   final Color _validFieldValueColor = const Color.fromRGBO(45, 194, 98, 0.2);
-  String _volatilePassword;
+  String? _volatilePassword;
   String _passwordInInputBox = '';
   String _passwordInInputBox = '';
   String _passwordInInputConfirmationBox = '';
   String _passwordInInputConfirmationBox = '';
   double _passwordStrength = 0.0;
   double _passwordStrength = 0.0;
@@ -62,7 +60,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
     if (_volatilePassword != null) {
     if (_volatilePassword != null) {
       Future.delayed(
       Future.delayed(
         Duration.zero,
         Duration.zero,
-        () => _showRecoveryCodeDialog(_volatilePassword),
+        () => _showRecoveryCodeDialog(_volatilePassword!),
       );
       );
     }
     }
     _password1FocusNode.addListener(() {
     _password1FocusNode.addListener(() {
@@ -81,7 +79,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
 
 
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -167,7 +165,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                     textAlign: TextAlign.start,
                     textAlign: TextAlign.start,
                     style: Theme.of(context)
                     style: Theme.of(context)
                         .textTheme
                         .textTheme
-                        .subtitle1
+                        .subtitle1!
                         .copyWith(fontSize: 14),
                         .copyWith(fontSize: 14),
                   ),
                   ),
                 ),
                 ),
@@ -178,7 +176,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                     text: TextSpan(
                     text: TextSpan(
                       style: Theme.of(context)
                       style: Theme.of(context)
                           .textTheme
                           .textTheme
-                          .subtitle1
+                          .subtitle1!
                           .copyWith(fontSize: 14),
                           .copyWith(fontSize: 14),
                       children: [
                       children: [
                         const TextSpan(
                         const TextSpan(
@@ -187,10 +185,11 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                         ),
                         ),
                         TextSpan(
                         TextSpan(
                           text: "we cannot decrypt your data",
                           text: "we cannot decrypt your data",
-                          style: Theme.of(context).textTheme.subtitle1.copyWith(
-                                fontSize: 14,
-                                decoration: TextDecoration.underline,
-                              ),
+                          style:
+                              Theme.of(context).textTheme.subtitle1!.copyWith(
+                                    fontSize: 14,
+                                    decoration: TextDecoration.underline,
+                                  ),
                         ),
                         ),
                       ],
                       ],
                     ),
                     ),
@@ -245,7 +244,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                                   Icons.check,
                                   Icons.check,
                                   color: Theme.of(context)
                                   color: Theme.of(context)
                                       .inputDecorationTheme
                                       .inputDecorationTheme
-                                      .focusedBorder
+                                      .focusedBorder!
                                       .borderSide
                                       .borderSide
                                       .color,
                                       .color,
                                 )
                                 )
@@ -307,7 +306,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                                   Icons.check,
                                   Icons.check,
                                   color: Theme.of(context)
                                   color: Theme.of(context)
                                       .inputDecorationTheme
                                       .inputDecorationTheme
-                                      .focusedBorder
+                                      .focusedBorder!
                                       .borderSide
                                       .borderSide
                                       .color,
                                       .color,
                                 )
                                 )
@@ -321,8 +320,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                     onChanged: (cnfPassword) {
                     onChanged: (cnfPassword) {
                       setState(() {
                       setState(() {
                         _passwordInInputConfirmationBox = cnfPassword;
                         _passwordInInputConfirmationBox = cnfPassword;
-                        if (_passwordInInputBox != null ||
-                            _passwordInInputBox != '') {
+                        if (_passwordInInputBox != '') {
                           _passwordsMatch = _passwordInInputBox ==
                           _passwordsMatch = _passwordInInputBox ==
                               _passwordInInputConfirmationBox;
                               _passwordInInputConfirmationBox;
                         }
                         }
@@ -364,7 +362,7 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
                     child: RichText(
                     child: RichText(
                       text: TextSpan(
                       text: TextSpan(
                         text: "How it works",
                         text: "How it works",
-                        style: Theme.of(context).textTheme.subtitle1.copyWith(
+                        style: Theme.of(context).textTheme.subtitle1!.copyWith(
                               fontSize: 14,
                               fontSize: 14,
                               decoration: TextDecoration.underline,
                               decoration: TextDecoration.underline,
                             ),
                             ),

+ 6 - 15
lib/ui/account/password_reentry_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -8,7 +6,6 @@ import 'package:photos/core/configuration.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/events/subscription_purchased_event.dart';
 import 'package:photos/events/subscription_purchased_event.dart';
-import 'package:photos/models/key_attributes.dart';
 import 'package:photos/ui/account/recovery_page.dart';
 import 'package:photos/ui/account/recovery_page.dart';
 import 'package:photos/ui/common/dialogs.dart';
 import 'package:photos/ui/common/dialogs.dart';
 import 'package:photos/ui/common/dynamic_fab.dart';
 import 'package:photos/ui/common/dynamic_fab.dart';
@@ -17,7 +14,7 @@ import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/email_util.dart';
 import 'package:photos/utils/email_util.dart';
 
 
 class PasswordReentryPage extends StatefulWidget {
 class PasswordReentryPage extends StatefulWidget {
-  const PasswordReentryPage({Key key}) : super(key: key);
+  const PasswordReentryPage({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<PasswordReentryPage> createState() => _PasswordReentryPageState();
   State<PasswordReentryPage> createState() => _PasswordReentryPageState();
@@ -27,7 +24,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
   final _logger = Logger((_PasswordReentryPageState).toString());
   final _logger = Logger((_PasswordReentryPageState).toString());
   final _passwordController = TextEditingController();
   final _passwordController = TextEditingController();
   final FocusNode _passwordFocusNode = FocusNode();
   final FocusNode _passwordFocusNode = FocusNode();
-  String email;
+  String? email;
   bool _passwordInFocus = false;
   bool _passwordInFocus = false;
   bool _passwordVisible = false;
   bool _passwordVisible = false;
 
 
@@ -46,7 +43,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
 
 
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -78,7 +75,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
           try {
           try {
             await Configuration.instance.decryptAndSaveSecrets(
             await Configuration.instance.decryptAndSaveSecrets(
               _passwordController.text,
               _passwordController.text,
-              Configuration.instance.getKeyAttributes(),
+              Configuration.instance.getKeyAttributes()!,
             );
             );
           } on KeyDerivationError catch (e, s) {
           } on KeyDerivationError catch (e, s) {
             _logger.severe("Password verification failed", e, s);
             _logger.severe("Password verification failed", e, s);
@@ -245,7 +242,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
                           child: Text(
                           child: Text(
                             "Forgot password",
                             "Forgot password",
                             style:
                             style:
-                                Theme.of(context).textTheme.subtitle1.copyWith(
+                                Theme.of(context).textTheme.subtitle1!.copyWith(
                                       fontSize: 14,
                                       fontSize: 14,
                                       decoration: TextDecoration.underline,
                                       decoration: TextDecoration.underline,
                                     ),
                                     ),
@@ -267,7 +264,7 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
                           child: Text(
                           child: Text(
                             "Change email",
                             "Change email",
                             style:
                             style:
-                                Theme.of(context).textTheme.subtitle1.copyWith(
+                                Theme.of(context).textTheme.subtitle1!.copyWith(
                                       fontSize: 14,
                                       fontSize: 14,
                                       decoration: TextDecoration.underline,
                                       decoration: TextDecoration.underline,
                                     ),
                                     ),
@@ -284,10 +281,4 @@ class _PasswordReentryPageState extends State<PasswordReentryPage> {
       ],
       ],
     );
     );
   }
   }
-
-  void validatePreVerificationState(KeyAttributes keyAttributes) {
-    if (keyAttributes == null) {
-      throw Exception("Key Attributes can not be null");
-    }
-  }
 }
 }

+ 13 - 13
lib/ui/account/recovery_key_page.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:io' as io;
 import 'dart:io' as io;
 
 
@@ -15,20 +15,20 @@ import 'package:share_plus/share_plus.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 
 
 class RecoveryKeyPage extends StatefulWidget {
 class RecoveryKeyPage extends StatefulWidget {
-  final bool showAppBar;
+  final bool? showAppBar;
   final String recoveryKey;
   final String recoveryKey;
   final String doneText;
   final String doneText;
-  final Function() onDone;
-  final bool isDismissible;
-  final String title;
-  final String text;
-  final String subText;
+  final Function()? onDone;
+  final bool? isDismissible;
+  final String? title;
+  final String? text;
+  final String? subText;
   final bool showProgressBar;
   final bool showProgressBar;
 
 
   const RecoveryKeyPage(
   const RecoveryKeyPage(
     this.recoveryKey,
     this.recoveryKey,
     this.doneText, {
     this.doneText, {
-    Key key,
+    Key? key,
     this.showAppBar,
     this.showAppBar,
     this.onDone,
     this.onDone,
     this.isDismissible,
     this.isDismissible,
@@ -56,7 +56,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
         'recovery code should have $mnemonicKeyWordCount words',
         'recovery code should have $mnemonicKeyWordCount words',
       );
       );
     }
     }
-    final double topPadding = widget.showAppBar
+    final double topPadding = widget.showAppBar!
         ? 40
         ? 40
         : widget.showProgressBar
         : widget.showProgressBar
             ? 32
             ? 32
@@ -79,7 +79,7 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
                 ),
                 ),
               ),
               ),
             )
             )
-          : widget.showAppBar
+          : widget.showAppBar!
               ? AppBar(
               ? AppBar(
                   elevation: 0,
                   elevation: 0,
                   title: Text(widget.title ?? "Recovery key"),
                   title: Text(widget.title ?? "Recovery key"),
@@ -100,14 +100,14 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
                     mainAxisSize: MainAxisSize.max,
                     mainAxisSize: MainAxisSize.max,
                     crossAxisAlignment: CrossAxisAlignment.start,
                     crossAxisAlignment: CrossAxisAlignment.start,
                     children: [
                     children: [
-                      widget.showAppBar
+                      widget.showAppBar!
                           ? const SizedBox.shrink()
                           ? const SizedBox.shrink()
                           : Text(
                           : Text(
                               widget.title ?? "Recovery key",
                               widget.title ?? "Recovery key",
                               style: Theme.of(context).textTheme.headline4,
                               style: Theme.of(context).textTheme.headline4,
                             ),
                             ),
                       Padding(
                       Padding(
-                        padding: EdgeInsets.all(widget.showAppBar ? 0 : 12),
+                        padding: EdgeInsets.all(widget.showAppBar! ? 0 : 12),
                       ),
                       ),
                       Text(
                       Text(
                         widget.text ??
                         widget.text ??
@@ -263,6 +263,6 @@ class _RecoveryKeyPageState extends State<RecoveryKeyPage> {
     if (_recoveryKeyFile.existsSync()) {
     if (_recoveryKeyFile.existsSync()) {
       await _recoveryKeyFile.delete();
       await _recoveryKeyFile.delete();
     }
     }
-    widget.onDone();
+    widget.onDone!();
   }
   }
 }
 }

+ 4 - 4
lib/ui/account/recovery_page.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:ui';
 import 'dart:ui';
 
 
@@ -10,7 +10,7 @@ import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/toast_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 
 class RecoveryPage extends StatefulWidget {
 class RecoveryPage extends StatefulWidget {
-  const RecoveryPage({Key key}) : super(key: key);
+  const RecoveryPage({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<RecoveryPage> createState() => _RecoveryPageState();
   State<RecoveryPage> createState() => _RecoveryPageState();
@@ -22,7 +22,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
     final isKeypadOpen = MediaQuery.of(context).viewInsets.bottom > 100;
-    FloatingActionButtonLocation fabLocation() {
+    FloatingActionButtonLocation? fabLocation() {
       if (isKeypadOpen) {
       if (isKeypadOpen) {
         return null;
         return null;
       } else {
       } else {
@@ -140,7 +140,7 @@ class _RecoveryPageState extends State<RecoveryPage> {
                           child: Text(
                           child: Text(
                             "No recovery key?",
                             "No recovery key?",
                             style:
                             style:
-                                Theme.of(context).textTheme.subtitle1.copyWith(
+                                Theme.of(context).textTheme.subtitle1!.copyWith(
                                       fontSize: 14,
                                       fontSize: 14,
                                       decoration: TextDecoration.underline,
                                       decoration: TextDecoration.underline,
                                     ),
                                     ),

+ 0 - 1
lib/ui/actions/collection/collection_file_actions.dart

@@ -1,5 +1,4 @@
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
-import 'package:flutter/widgets.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/db/files_db.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/collection.dart';
 import 'package:photos/models/file.dart';
 import 'package:photos/models/file.dart';

+ 1 - 2
lib/ui/actions/collection/collection_sharing_actions.dart

@@ -94,8 +94,7 @@ class CollectionActions {
         subType: subTypeSharedFilesCollection,
         subType: subTypeSharedFilesCollection,
       );
       );
       final collection = await collectionsService.createAndCacheCollection(
       final collection = await collectionsService.createAndCacheCollection(
-        null,
-        createRequest: req,
+        req,
       );
       );
       logger.finest("adding files to share to new album");
       logger.finest("adding files to share to new album");
       await collectionsService.addToCollection(collection.id, files);
       await collectionsService.addToCollection(collection.id, files);

+ 18 - 20
lib/ui/backup_folder_selection_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:io';
 import 'dart:io';
 import 'dart:ui';
 import 'dart:ui';
 
 
@@ -24,9 +22,9 @@ class BackupFolderSelectionPage extends StatefulWidget {
   final String buttonText;
   final String buttonText;
 
 
   const BackupFolderSelectionPage({
   const BackupFolderSelectionPage({
-    @required this.buttonText,
+    required this.buttonText,
     this.isOnboarding = false,
     this.isOnboarding = false,
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -38,8 +36,8 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
   final Logger _logger = Logger((_BackupFolderSelectionPageState).toString());
   final Logger _logger = Logger((_BackupFolderSelectionPageState).toString());
   final Set<String> _allDevicePathIDs = <String>{};
   final Set<String> _allDevicePathIDs = <String>{};
   final Set<String> _selectedDevicePathIDs = <String>{};
   final Set<String> _selectedDevicePathIDs = <String>{};
-  List<DeviceCollection> _deviceCollections;
-  Map<String, int> _pathIDToItemCount;
+  List<DeviceCollection>? _deviceCollections;
+  Map<String, int>? _pathIDToItemCount;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -50,10 +48,10 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
           await FilesDB.instance.getDevicePathIDToImportedFileCount();
           await FilesDB.instance.getDevicePathIDToImportedFileCount();
       setState(() {
       setState(() {
         _deviceCollections = files;
         _deviceCollections = files;
-        _deviceCollections.sort((first, second) {
+        _deviceCollections!.sort((first, second) {
           return first.name.toLowerCase().compareTo(second.name.toLowerCase());
           return first.name.toLowerCase().compareTo(second.name.toLowerCase());
         });
         });
-        for (final file in _deviceCollections) {
+        for (final file in _deviceCollections!) {
           _allDevicePathIDs.add(file.id);
           _allDevicePathIDs.add(file.id);
           if (file.shouldBackup) {
           if (file.shouldBackup) {
             _selectedDevicePathIDs.add(file.id);
             _selectedDevicePathIDs.add(file.id);
@@ -103,7 +101,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
             padding: const EdgeInsets.only(left: 24, right: 48),
             padding: const EdgeInsets.only(left: 24, right: 48),
             child: Text(
             child: Text(
               "Selected folders will be encrypted and backed up",
               "Selected folders will be encrypted and backed up",
-              style: Theme.of(context).textTheme.caption.copyWith(height: 1.3),
+              style: Theme.of(context).textTheme.caption!.copyWith(height: 1.3),
             ),
             ),
           ),
           ),
           const Padding(
           const Padding(
@@ -139,7 +137,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                     } else {
                     } else {
                       _selectedDevicePathIDs.addAll(_allDevicePathIDs);
                       _selectedDevicePathIDs.addAll(_allDevicePathIDs);
                     }
                     }
-                    _deviceCollections.sort((first, second) {
+                    _deviceCollections!.sort((first, second) {
                       return first.name
                       return first.name
                           .toLowerCase()
                           .toLowerCase()
                           .compareTo(second.name.toLowerCase());
                           .compareTo(second.name.toLowerCase());
@@ -191,7 +189,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                         },
                         },
                         child: Text(
                         child: Text(
                           "Skip",
                           "Skip",
-                          style: Theme.of(context).textTheme.caption.copyWith(
+                          style: Theme.of(context).textTheme.caption!.copyWith(
                                 decoration: TextDecoration.underline,
                                 decoration: TextDecoration.underline,
                               ),
                               ),
                         ),
                         ),
@@ -247,11 +245,11 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
           padding: const EdgeInsets.only(right: 4),
           padding: const EdgeInsets.only(right: 4),
           child: ImplicitlyAnimatedReorderableList<DeviceCollection>(
           child: ImplicitlyAnimatedReorderableList<DeviceCollection>(
             controller: scrollController,
             controller: scrollController,
-            items: _deviceCollections,
+            items: _deviceCollections!,
             areItemsTheSame: (oldItem, newItem) => oldItem.id == newItem.id,
             areItemsTheSame: (oldItem, newItem) => oldItem.id == newItem.id,
             onReorderFinished: (item, from, to, newItems) {
             onReorderFinished: (item, from, to, newItems) {
               setState(() {
               setState(() {
-                _deviceCollections
+                _deviceCollections!
                   ..clear()
                   ..clear()
                   ..addAll(newItems);
                   ..addAll(newItems);
               });
               });
@@ -261,7 +259,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                 key: ValueKey(file),
                 key: ValueKey(file),
                 builder: (context, dragAnimation, inDrag) {
                 builder: (context, dragAnimation, inDrag) {
                   final t = dragAnimation.value;
                   final t = dragAnimation.value;
-                  final elevation = lerpDouble(0, 8, t);
+                  final elevation = lerpDouble(0, 8, t)!;
                   final themeColor = Theme.of(context).colorScheme.onSurface;
                   final themeColor = Theme.of(context).colorScheme.onSurface;
                   final color =
                   final color =
                       Color.lerp(themeColor, themeColor.withOpacity(0.8), t);
                       Color.lerp(themeColor, themeColor.withOpacity(0.8), t);
@@ -288,7 +286,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
   Widget _getFileItem(DeviceCollection deviceCollection) {
   Widget _getFileItem(DeviceCollection deviceCollection) {
     final isSelected = _selectedDevicePathIDs.contains(deviceCollection.id);
     final isSelected = _selectedDevicePathIDs.contains(deviceCollection.id);
     final importedCount = _pathIDToItemCount != null
     final importedCount = _pathIDToItemCount != null
-        ? _pathIDToItemCount[deviceCollection.id] ?? 0
+        ? _pathIDToItemCount![deviceCollection.id] ?? 0
         : -1;
         : -1;
     return Padding(
     return Padding(
       padding: const EdgeInsets.only(bottom: 1, right: 1),
       padding: const EdgeInsets.only(bottom: 1, right: 1),
@@ -326,7 +324,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                     activeColor: Colors.white,
                     activeColor: Colors.white,
                     value: isSelected,
                     value: isSelected,
                     onChanged: (value) {
                     onChanged: (value) {
-                      if (value) {
+                      if (value!) {
                         _selectedDevicePathIDs.add(deviceCollection.id);
                         _selectedDevicePathIDs.add(deviceCollection.id);
                       } else {
                       } else {
                         _selectedDevicePathIDs.remove(deviceCollection.id);
                         _selectedDevicePathIDs.remove(deviceCollection.id);
@@ -360,9 +358,9 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                       const Padding(padding: EdgeInsets.only(top: 2)),
                       const Padding(padding: EdgeInsets.only(top: 2)),
                       Text(
                       Text(
                         (kDebugMode ? 'inApp: $importedCount : device ' : '') +
                         (kDebugMode ? 'inApp: $importedCount : device ' : '') +
-                            (deviceCollection.count ?? 0).toString() +
+                            (deviceCollection.count).toString() +
                             " item" +
                             " item" +
-                            ((deviceCollection.count ?? 0) == 1 ? "" : "s"),
+                            ((deviceCollection.count) == 1 ? "" : "s"),
                         textAlign: TextAlign.left,
                         textAlign: TextAlign.left,
                         style: TextStyle(
                         style: TextStyle(
                           fontSize: 12,
                           fontSize: 12,
@@ -375,7 +373,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
                   ),
                   ),
                 ],
                 ],
               ),
               ),
-              _getThumbnail(deviceCollection.thumbnail, isSelected),
+              _getThumbnail(deviceCollection.thumbnail!, isSelected),
             ],
             ],
           ),
           ),
           onTap: () {
           onTap: () {
@@ -393,7 +391,7 @@ class _BackupFolderSelectionPageState extends State<BackupFolderSelectionPage> {
   }
   }
 
 
   void _sortFiles() {
   void _sortFiles() {
-    _deviceCollections.sort((first, second) {
+    _deviceCollections!.sort((first, second) {
       if (_selectedDevicePathIDs.contains(first.id) &&
       if (_selectedDevicePathIDs.contains(first.id) &&
           _selectedDevicePathIDs.contains(second.id)) {
           _selectedDevicePathIDs.contains(second.id)) {
         return first.name.toLowerCase().compareTo(second.name.toLowerCase());
         return first.name.toLowerCase().compareTo(second.name.toLowerCase());

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

@@ -45,7 +45,7 @@ class ArchivedCollectionsButtonWidget extends StatelessWidget {
                   FutureBuilder<int>(
                   FutureBuilder<int>(
                     future: FilesDB.instance.fileCountWithVisibility(
                     future: FilesDB.instance.fileCountWithVisibility(
                       visibilityArchive,
                       visibilityArchive,
-                      Configuration.instance.getUserID(),
+                      Configuration.instance.getUserID()!,
                     ),
                     ),
                     builder: (context, snapshot) {
                     builder: (context, snapshot) {
                       if (snapshot.hasData && snapshot.data! > 0) {
                       if (snapshot.hasData && snapshot.data! > 0) {

+ 7 - 7
lib/ui/collections/device_folders_grid_view_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:async';
 import 'dart:async';
 
 
@@ -17,7 +17,7 @@ import 'package:photos/ui/viewer/gallery/empty_state.dart';
 
 
 class DeviceFoldersGridViewWidget extends StatefulWidget {
 class DeviceFoldersGridViewWidget extends StatefulWidget {
   const DeviceFoldersGridViewWidget({
   const DeviceFoldersGridViewWidget({
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -27,8 +27,8 @@ class DeviceFoldersGridViewWidget extends StatefulWidget {
 
 
 class _DeviceFoldersGridViewWidgetState
 class _DeviceFoldersGridViewWidgetState
     extends State<DeviceFoldersGridViewWidget> {
     extends State<DeviceFoldersGridViewWidget> {
-  StreamSubscription<BackupFoldersUpdatedEvent> _backupFoldersUpdatedEvent;
-  StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
+  StreamSubscription<BackupFoldersUpdatedEvent>? _backupFoldersUpdatedEvent;
+  StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription;
   String _loadReason = "init";
   String _loadReason = "init";
 
 
   @override
   @override
@@ -65,7 +65,7 @@ class _DeviceFoldersGridViewWidgetState
                 .getDeviceCollections(includeCoverThumbnail: true),
                 .getDeviceCollections(includeCoverThumbnail: true),
             builder: (context, snapshot) {
             builder: (context, snapshot) {
               if (snapshot.hasData) {
               if (snapshot.hasData) {
-                return snapshot.data.isEmpty
+                return snapshot.data!.isEmpty
                     ? Padding(
                     ? Padding(
                         padding: const EdgeInsets.all(22),
                         padding: const EdgeInsets.all(22),
                         child: (isMigrationDone
                         child: (isMigrationDone
@@ -81,10 +81,10 @@ class _DeviceFoldersGridViewWidgetState
                         physics: const ScrollPhysics(),
                         physics: const ScrollPhysics(),
                         // to disable GridView's scrolling
                         // to disable GridView's scrolling
                         itemBuilder: (context, index) {
                         itemBuilder: (context, index) {
-                          final deviceCollection = snapshot.data[index];
+                          final deviceCollection = snapshot.data![index];
                           return DeviceFolderIcon(deviceCollection);
                           return DeviceFolderIcon(deviceCollection);
                         },
                         },
-                        itemCount: snapshot.data.length,
+                        itemCount: snapshot.data!.length,
                       );
                       );
               } else if (snapshot.hasError) {
               } else if (snapshot.hasError) {
                 logger.severe("failed to load device gallery", snapshot.error);
                 logger.severe("failed to load device gallery", snapshot.error);

+ 3 - 3
lib/ui/collections/hidden_collections_button_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/services/local_authentication_service.dart';
 import 'package:photos/services/local_authentication_service.dart';
@@ -10,7 +10,7 @@ class HiddenCollectionsButtonWidget extends StatelessWidget {
 
 
   const HiddenCollectionsButtonWidget(
   const HiddenCollectionsButtonWidget(
     this.textStyle, {
     this.textStyle, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -24,7 +24,7 @@ class HiddenCollectionsButtonWidget extends StatelessWidget {
         padding: const EdgeInsets.all(0),
         padding: const EdgeInsets.all(0),
         side: BorderSide(
         side: BorderSide(
           width: 0.5,
           width: 0.5,
-          color: Theme.of(context).iconTheme.color.withOpacity(0.24),
+          color: Theme.of(context).iconTheme.color!.withOpacity(0.24),
         ),
         ),
       ),
       ),
       child: SizedBox(
       child: SizedBox(

+ 6 - 6
lib/ui/collections/remote_collections_grid_view_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:math';
 import 'dart:math';
 
 
@@ -17,11 +17,11 @@ class RemoteCollectionsGridViewWidget extends StatelessWidget {
   static const fixedGapBetweenAlbum = 8.0;
   static const fixedGapBetweenAlbum = 8.0;
   static const minGapForHorizontalPadding = 8.0;
   static const minGapForHorizontalPadding = 8.0;
 
 
-  final List<CollectionWithThumbnail> collections;
+  final List<CollectionWithThumbnail>? collections;
 
 
   const RemoteCollectionsGridViewWidget(
   const RemoteCollectionsGridViewWidget(
     this.collections, {
     this.collections, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -46,13 +46,13 @@ class RemoteCollectionsGridViewWidget extends StatelessWidget {
         physics: const ScrollPhysics(),
         physics: const ScrollPhysics(),
         // to disable GridView's scrolling
         // to disable GridView's scrolling
         itemBuilder: (context, index) {
         itemBuilder: (context, index) {
-          if (index < collections.length) {
-            return CollectionItem(collections[index], sideOfThumbnail);
+          if (index < collections!.length) {
+            return CollectionItem(collections![index], sideOfThumbnail);
           } else {
           } else {
             return const CreateNewAlbumWidget();
             return const CreateNewAlbumWidget();
           }
           }
         },
         },
-        itemCount: collections.length + 1,
+        itemCount: collections!.length + 1,
         // To include the + button
         // To include the + button
         gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
         gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
           crossAxisCount: albumsCountInOneRow,
           crossAxisCount: albumsCountInOneRow,

+ 23 - 15
lib/ui/collections_gallery_widget.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:collection/collection.dart';
 import 'package:collection/collection.dart';
@@ -22,11 +20,12 @@ import 'package:photos/ui/collections/remote_collections_grid_view_widget.dart';
 import 'package:photos/ui/collections/section_title.dart';
 import 'package:photos/ui/collections/section_title.dart';
 import 'package:photos/ui/collections/trash_button_widget.dart';
 import 'package:photos/ui/collections/trash_button_widget.dart';
 import 'package:photos/ui/common/loading_widget.dart';
 import 'package:photos/ui/common/loading_widget.dart';
+import 'package:photos/ui/viewer/actions/delete_empty_albums.dart';
 import 'package:photos/ui/viewer/gallery/empty_state.dart';
 import 'package:photos/ui/viewer/gallery/empty_state.dart';
 import 'package:photos/utils/local_settings.dart';
 import 'package:photos/utils/local_settings.dart';
 
 
 class CollectionsGalleryWidget extends StatefulWidget {
 class CollectionsGalleryWidget extends StatefulWidget {
-  const CollectionsGalleryWidget({Key key}) : super(key: key);
+  const CollectionsGalleryWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<CollectionsGalleryWidget> createState() =>
   State<CollectionsGalleryWidget> createState() =>
@@ -36,10 +35,11 @@ class CollectionsGalleryWidget extends StatefulWidget {
 class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
 class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
     with AutomaticKeepAliveClientMixin {
     with AutomaticKeepAliveClientMixin {
   final _logger = Logger((_CollectionsGalleryWidgetState).toString());
   final _logger = Logger((_CollectionsGalleryWidgetState).toString());
-  StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
-  StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
-  StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
-  AlbumSortKey sortKey;
+  late StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
+  late StreamSubscription<CollectionUpdatedEvent>
+      _collectionUpdatesSubscription;
+  late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
+  AlbumSortKey? sortKey;
   String _loadReason = "init";
   String _loadReason = "init";
 
 
   @override
   @override
@@ -98,8 +98,8 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
       (first, second) {
       (first, second) {
         if (sortKey == AlbumSortKey.albumName) {
         if (sortKey == AlbumSortKey.albumName) {
           return compareAsciiLowerCaseNatural(
           return compareAsciiLowerCaseNatural(
-            first.collection.name,
-            second.collection.name,
+            first.collection.name!,
+            second.collection.name!,
           );
           );
         } else if (sortKey == AlbumSortKey.newestPhoto) {
         } else if (sortKey == AlbumSortKey.newestPhoto) {
           return (second.thumbnail?.creationTime ?? -1 * intMaxValue)
           return (second.thumbnail?.creationTime ?? -1 * intMaxValue)
@@ -121,13 +121,15 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
   }
   }
 
 
   Widget _getCollectionsGalleryWidget(
   Widget _getCollectionsGalleryWidget(
-    List<CollectionWithThumbnail> collections,
+    List<CollectionWithThumbnail>? collections,
   ) {
   ) {
+    final bool showDeleteAlbumsButton =
+        collections!.where((c) => c.thumbnail == null).length >= 3;
     final TextStyle trashAndHiddenTextStyle = Theme.of(context)
     final TextStyle trashAndHiddenTextStyle = Theme.of(context)
         .textTheme
         .textTheme
-        .subtitle1
+        .subtitle1!
         .copyWith(
         .copyWith(
-          color: Theme.of(context).textTheme.subtitle1.color.withOpacity(0.5),
+          color: Theme.of(context).textTheme.subtitle1!.color!.withOpacity(0.5),
         );
         );
 
 
     return SingleChildScrollView(
     return SingleChildScrollView(
@@ -149,6 +151,12 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
                 _sortMenu(),
                 _sortMenu(),
               ],
               ],
             ),
             ),
+            showDeleteAlbumsButton
+                ? const Padding(
+                    padding: EdgeInsets.only(top: 2, left: 8.5, right: 48),
+                    child: DeleteEmptyAlbums(),
+                  )
+                : const SizedBox.shrink(),
             const SizedBox(height: 12),
             const SizedBox(height: 12),
             Configuration.instance.hasConfiguredAccount()
             Configuration.instance.hasConfiguredAccount()
                 ? RemoteCollectionsGridViewWidget(collections)
                 ? RemoteCollectionsGridViewWidget(collections)
@@ -190,9 +198,9 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
       }
       }
       return Text(
       return Text(
         text,
         text,
-        style: Theme.of(context).textTheme.subtitle1.copyWith(
+        style: Theme.of(context).textTheme.subtitle1!.copyWith(
               fontSize: 14,
               fontSize: 14,
-              color: Theme.of(context).iconTheme.color.withOpacity(0.7),
+              color: Theme.of(context).iconTheme.color!.withOpacity(0.7),
             ),
             ),
       );
       );
     }
     }
@@ -228,7 +236,7 @@ class _CollectionsGalleryWidgetState extends State<CollectionsGalleryWidget>
         ),
         ),
         onSelected: (int index) async {
         onSelected: (int index) async {
           sortKey = AlbumSortKey.values[index];
           sortKey = AlbumSortKey.values[index];
-          await LocalSettings.instance.setAlbumSortKey(sortKey);
+          await LocalSettings.instance.setAlbumSortKey(sortKey!);
           setState(() {});
           setState(() {});
         },
         },
         itemBuilder: (context) {
         itemBuilder: (context) {

+ 2 - 2
lib/ui/common/DividerWithPadding.dart

@@ -1,11 +1,11 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
 class DividerWithPadding extends StatelessWidget {
 class DividerWithPadding extends StatelessWidget {
   final double left, top, right, bottom, thinckness;
   final double left, top, right, bottom, thinckness;
   const DividerWithPadding({
   const DividerWithPadding({
-    Key key,
+    Key? key,
     this.left = 0,
     this.left = 0,
     this.top = 0,
     this.top = 0,
     this.right = 0,
     this.right = 0,

+ 3 - 3
lib/ui/common/bottom_shadow.dart

@@ -1,11 +1,11 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
 class BottomShadowWidget extends StatelessWidget {
 class BottomShadowWidget extends StatelessWidget {
   final double offsetDy;
   final double offsetDy;
-  final Color shadowColor;
-  const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key key})
+  final Color? shadowColor;
+  const BottomShadowWidget({this.offsetDy = 28, this.shadowColor, Key? key})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override

+ 4 - 4
lib/ui/common/dialogs.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
@@ -11,14 +11,14 @@ enum ActionType {
 }
 }
 
 
 // if dialog is dismissed by tapping outside, this will return null
 // if dialog is dismissed by tapping outside, this will return null
-Future<DialogUserChoice> showChoiceDialog<T>(
+Future<DialogUserChoice?> showChoiceDialog<T>(
   BuildContext context,
   BuildContext context,
   String title,
   String title,
   String content, {
   String content, {
   String firstAction = 'Ok',
   String firstAction = 'Ok',
-  Color firstActionColor,
+  Color? firstActionColor,
   String secondAction = 'Cancel',
   String secondAction = 'Cancel',
-  Color secondActionColor,
+  Color? secondActionColor,
   ActionType actionType = ActionType.confirm,
   ActionType actionType = ActionType.confirm,
 }) {
 }) {
   final AlertDialog alert = AlertDialog(
   final AlertDialog alert = AlertDialog(

+ 15 - 15
lib/ui/common/dynamic_fab.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:math' as math;
 import 'dart:math' as math;
 
 
@@ -6,13 +6,13 @@ import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 
 
 class DynamicFAB extends StatelessWidget {
 class DynamicFAB extends StatelessWidget {
-  final bool isKeypadOpen;
-  final bool isFormValid;
-  final String buttonText;
-  final Function onPressedFunction;
+  final bool? isKeypadOpen;
+  final bool? isFormValid;
+  final String? buttonText;
+  final Function? onPressedFunction;
 
 
   const DynamicFAB({
   const DynamicFAB({
-    Key key,
+    Key? key,
     this.isKeypadOpen,
     this.isKeypadOpen,
     this.buttonText,
     this.buttonText,
     this.isFormValid,
     this.isFormValid,
@@ -21,7 +21,7 @@ class DynamicFAB extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    if (isKeypadOpen) {
+    if (isKeypadOpen!) {
       return Container(
       return Container(
         decoration: BoxDecoration(
         decoration: BoxDecoration(
           boxShadow: [
           boxShadow: [
@@ -43,13 +43,13 @@ class DynamicFAB extends StatelessWidget {
                   Theme.of(context).colorScheme.dynamicFABBackgroundColor,
                   Theme.of(context).colorScheme.dynamicFABBackgroundColor,
               foregroundColor:
               foregroundColor:
                   Theme.of(context).colorScheme.dynamicFABTextColor,
                   Theme.of(context).colorScheme.dynamicFABTextColor,
-              onPressed: isFormValid
-                  ? onPressedFunction
+              onPressed: isFormValid!
+                  ? onPressedFunction as void Function()?
                   : () {
                   : () {
                       FocusScope.of(context).unfocus();
                       FocusScope.of(context).unfocus();
                     },
                     },
               child: Transform.rotate(
               child: Transform.rotate(
-                angle: isFormValid ? 0 : math.pi / 2,
+                angle: isFormValid! ? 0 : math.pi / 2,
                 child: const Icon(
                 child: const Icon(
                   Icons.chevron_right,
                   Icons.chevron_right,
                   size: 36,
                   size: 36,
@@ -65,8 +65,8 @@ class DynamicFAB extends StatelessWidget {
         height: 56,
         height: 56,
         padding: const EdgeInsets.symmetric(horizontal: 20),
         padding: const EdgeInsets.symmetric(horizontal: 20),
         child: OutlinedButton(
         child: OutlinedButton(
-          onPressed: isFormValid ? onPressedFunction : null,
-          child: Text(buttonText),
+          onPressed: isFormValid! ? onPressedFunction as void Function()? : null,
+          child: Text(buttonText!),
         ),
         ),
       );
       );
     }
     }
@@ -75,17 +75,17 @@ class DynamicFAB extends StatelessWidget {
 
 
 class NoScalingAnimation extends FloatingActionButtonAnimator {
 class NoScalingAnimation extends FloatingActionButtonAnimator {
   @override
   @override
-  Offset getOffset({Offset begin, Offset end, double progress}) {
+  Offset getOffset({Offset? begin, required Offset end, double? progress}) {
     return end;
     return end;
   }
   }
 
 
   @override
   @override
-  Animation<double> getRotationAnimation({Animation<double> parent}) {
+  Animation<double> getRotationAnimation({required Animation<double> parent}) {
     return Tween<double>(begin: 1.0, end: 1.0).animate(parent);
     return Tween<double>(begin: 1.0, end: 1.0).animate(parent);
   }
   }
 
 
   @override
   @override
-  Animation<double> getScaleAnimation({Animation<double> parent}) {
+  Animation<double> getScaleAnimation({required Animation<double> parent}) {
     return Tween<double>(begin: 1.0, end: 1.0).animate(parent);
     return Tween<double>(begin: 1.0, end: 1.0).animate(parent);
   }
   }
 }
 }

+ 5 - 5
lib/ui/common/gradient_button.dart

@@ -1,23 +1,23 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
 
 
 class GradientButton extends StatelessWidget {
 class GradientButton extends StatelessWidget {
   final List<Color> linearGradientColors;
   final List<Color> linearGradientColors;
-  final Function onTap;
+  final Function? onTap;
 
 
   // text is ignored if child is specified
   // text is ignored if child is specified
   final String text;
   final String text;
 
 
   // nullable
   // nullable
-  final IconData iconData;
+  final IconData? iconData;
 
 
   // padding between the text and icon
   // padding between the text and icon
   final double paddingValue;
   final double paddingValue;
 
 
   const GradientButton({
   const GradientButton({
-    Key key,
+    Key? key,
     this.linearGradientColors = const [
     this.linearGradientColors = const [
       Color(0xFF2CD267),
       Color(0xFF2CD267),
       Color(0xFF1DB954),
       Color(0xFF1DB954),
@@ -65,7 +65,7 @@ class GradientButton extends StatelessWidget {
       );
       );
     }
     }
     return InkWell(
     return InkWell(
-      onTap: onTap,
+      onTap: onTap as void Function()?,
       child: Container(
       child: Container(
         height: 56,
         height: 56,
         decoration: BoxDecoration(
         decoration: BoxDecoration(

+ 3 - 3
lib/ui/common/linear_progress_dialog.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
@@ -6,14 +6,14 @@ import 'package:photos/ente_theme_data.dart';
 class LinearProgressDialog extends StatefulWidget {
 class LinearProgressDialog extends StatefulWidget {
   final String message;
   final String message;
 
 
-  const LinearProgressDialog(this.message, {Key key}) : super(key: key);
+  const LinearProgressDialog(this.message, {Key? key}) : super(key: key);
 
 
   @override
   @override
   LinearProgressDialogState createState() => LinearProgressDialogState();
   LinearProgressDialogState createState() => LinearProgressDialogState();
 }
 }
 
 
 class LinearProgressDialogState extends State<LinearProgressDialog> {
 class LinearProgressDialogState extends State<LinearProgressDialog> {
-  double _progress;
+  double? _progress;
 
 
   @override
   @override
   void initState() {
   void initState() {

+ 36 - 36
lib/ui/common/progress_dialog.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
 enum ProgressDialogType { normal, download }
 enum ProgressDialogType { normal, download }
@@ -7,7 +5,7 @@ enum ProgressDialogType { normal, download }
 String _dialogMessage = "Loading...";
 String _dialogMessage = "Loading...";
 double _progress = 0.0, _maxProgress = 100.0;
 double _progress = 0.0, _maxProgress = 100.0;
 
 
-Widget _customBody;
+Widget? _customBody;
 
 
 TextAlign _textAlign = TextAlign.left;
 TextAlign _textAlign = TextAlign.left;
 Alignment _progressWidgetAlignment = Alignment.centerLeft;
 Alignment _progressWidgetAlignment = Alignment.centerLeft;
@@ -15,10 +13,10 @@ Alignment _progressWidgetAlignment = Alignment.centerLeft;
 TextDirection _direction = TextDirection.ltr;
 TextDirection _direction = TextDirection.ltr;
 
 
 bool _isShowing = false;
 bool _isShowing = false;
-BuildContext _context, _dismissingContext;
-ProgressDialogType _progressDialogType;
+BuildContext? _context, _dismissingContext;
+ProgressDialogType? _progressDialogType;
 bool _barrierDismissible = true, _showLogs = false;
 bool _barrierDismissible = true, _showLogs = false;
-Color _barrierColor;
+Color? _barrierColor;
 
 
 TextStyle _progressTextStyle = const TextStyle(
 TextStyle _progressTextStyle = const TextStyle(
       color: Colors.black,
       color: Colors.black,
@@ -42,16 +40,16 @@ Widget _progressWidget = Image.asset(
 );
 );
 
 
 class ProgressDialog {
 class ProgressDialog {
-  _Body _dialog;
+  _Body? _dialog;
 
 
   ProgressDialog(
   ProgressDialog(
     BuildContext context, {
     BuildContext context, {
-    ProgressDialogType type,
-    bool isDismissible,
-    bool showLogs,
-    TextDirection textDirection,
-    Widget customBody,
-    Color barrierColor,
+    ProgressDialogType? type,
+    bool? isDismissible,
+    bool? showLogs,
+    TextDirection? textDirection,
+    Widget? customBody,
+    Color? barrierColor,
   }) {
   }) {
     _context = context;
     _context = context;
     _progressDialogType = type ?? ProgressDialogType.normal;
     _progressDialogType = type ?? ProgressDialogType.normal;
@@ -63,20 +61,20 @@ class ProgressDialog {
   }
   }
 
 
   void style({
   void style({
-    Widget child,
-    double progress,
-    double maxProgress,
-    String message,
-    Widget progressWidget,
-    Color backgroundColor,
-    TextStyle progressTextStyle,
-    TextStyle messageTextStyle,
-    double elevation,
-    TextAlign textAlign,
-    double borderRadius,
-    Curve insetAnimCurve,
-    EdgeInsets padding,
-    Alignment progressWidgetAlignment,
+    Widget? child,
+    double? progress,
+    double? maxProgress,
+    String? message,
+    Widget? progressWidget,
+    Color? backgroundColor,
+    TextStyle? progressTextStyle,
+    TextStyle? messageTextStyle,
+    double? elevation,
+    TextAlign? textAlign,
+    double? borderRadius,
+    Curve? insetAnimCurve,
+    EdgeInsets? padding,
+    Alignment? progressWidgetAlignment,
   }) {
   }) {
     if (_isShowing) return;
     if (_isShowing) return;
     if (_progressDialogType == ProgressDialogType.download) {
     if (_progressDialogType == ProgressDialogType.download) {
@@ -100,12 +98,12 @@ class ProgressDialog {
   }
   }
 
 
   void update({
   void update({
-    double progress,
-    double maxProgress,
-    String message,
-    Widget progressWidget,
-    TextStyle progressTextStyle,
-    TextStyle messageTextStyle,
+    double? progress,
+    double? maxProgress,
+    String? message,
+    Widget? progressWidget,
+    TextStyle? progressTextStyle,
+    TextStyle? messageTextStyle,
   }) {
   }) {
     if (_progressDialogType == ProgressDialogType.download) {
     if (_progressDialogType == ProgressDialogType.download) {
       _progress = progress ?? _progress;
       _progress = progress ?? _progress;
@@ -117,7 +115,7 @@ class ProgressDialog {
     _messageStyle = messageTextStyle ?? _messageStyle;
     _messageStyle = messageTextStyle ?? _messageStyle;
     _progressTextStyle = progressTextStyle ?? _progressTextStyle;
     _progressTextStyle = progressTextStyle ?? _progressTextStyle;
 
 
-    if (_isShowing) _dialog.update();
+    if (_isShowing) _dialog!.update();
   }
   }
 
 
   bool isShowing() {
   bool isShowing() {
@@ -128,7 +126,9 @@ class ProgressDialog {
     try {
     try {
       if (_isShowing) {
       if (_isShowing) {
         _isShowing = false;
         _isShowing = false;
-        Navigator.of(_dismissingContext).pop();
+        if (_dismissingContext != null) {
+          Navigator.of(_dismissingContext!).pop();
+        }
         if (_showLogs) debugPrint('ProgressDialog dismissed');
         if (_showLogs) debugPrint('ProgressDialog dismissed');
         return Future.value(true);
         return Future.value(true);
       } else {
       } else {
@@ -147,7 +147,7 @@ class ProgressDialog {
       if (!_isShowing) {
       if (!_isShowing) {
         _dialog = _Body();
         _dialog = _Body();
         showDialog<dynamic>(
         showDialog<dynamic>(
-          context: _context,
+          context: _context!,
           barrierDismissible: _barrierDismissible,
           barrierDismissible: _barrierDismissible,
           barrierColor: _barrierColor,
           barrierColor: _barrierColor,
           builder: (BuildContext context) {
           builder: (BuildContext context) {

+ 7 - 7
lib/ui/common/rename_dialog.dart

@@ -1,14 +1,14 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 
 
 class RenameDialog extends StatefulWidget {
 class RenameDialog extends StatefulWidget {
-  final String name;
+  final String? name;
   final String type;
   final String type;
   final int maxLength;
   final int maxLength;
 
 
-  const RenameDialog(this.name, this.type, {Key key, this.maxLength = 100})
+  const RenameDialog(this.name, this.type, {Key? key, this.maxLength = 100})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override
@@ -16,7 +16,7 @@ class RenameDialog extends StatefulWidget {
 }
 }
 
 
 class _RenameDialogState extends State<RenameDialog> {
 class _RenameDialogState extends State<RenameDialog> {
-  String _newName;
+  String? _newName;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -74,7 +74,7 @@ class _RenameDialogState extends State<RenameDialog> {
             ),
             ),
           ),
           ),
           onPressed: () {
           onPressed: () {
-            if (_newName.trim().isEmpty) {
+            if (_newName!.trim().isEmpty) {
               showErrorDialog(
               showErrorDialog(
                 context,
                 context,
                 "Empty name",
                 "Empty name",
@@ -82,7 +82,7 @@ class _RenameDialogState extends State<RenameDialog> {
               );
               );
               return;
               return;
             }
             }
-            if (_newName.trim().length > widget.maxLength) {
+            if (_newName!.trim().length > widget.maxLength) {
               showErrorDialog(
               showErrorDialog(
                 context,
                 context,
                 "Name too large",
                 "Name too large",
@@ -90,7 +90,7 @@ class _RenameDialogState extends State<RenameDialog> {
               );
               );
               return;
               return;
             }
             }
-            Navigator.of(context).pop(_newName.trim());
+            Navigator.of(context).pop(_newName!.trim());
           },
           },
         ),
         ),
       ],
       ],

+ 2 - 2
lib/ui/common/web_page.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@@ -8,7 +8,7 @@ class WebPage extends StatefulWidget {
   final String title;
   final String title;
   final String url;
   final String url;
 
 
-  const WebPage(this.title, this.url, {Key key}) : super(key: key);
+  const WebPage(this.title, this.url, {Key? key}) : super(key: key);
 
 
   @override
   @override
   State<WebPage> createState() => _WebPageState();
   State<WebPage> createState() => _WebPageState();

+ 2 - 3
lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart

@@ -103,12 +103,11 @@ class BottomActionBarWidget extends StatelessWidget {
   }
   }
 
 
   List<Widget> _iconButtons(BuildContext context) {
   List<Widget> _iconButtons(BuildContext context) {
-    final iconButtonsWithExpansionIcon = <Widget?>[
+    final iconButtonsWithExpansionIcon = <Widget>[
       ...?iconButtons,
       ...?iconButtons,
       ExpansionIconWidget(expandableController: _expandableController)
       ExpansionIconWidget(expandableController: _expandableController)
     ];
     ];
-    iconButtonsWithExpansionIcon.removeWhere((element) => element == null);
-    return iconButtonsWithExpansionIcon as List<Widget>;
+    return iconButtonsWithExpansionIcon;
   }
   }
 
 
   ExpandableThemeData _getExpandableTheme() {
   ExpandableThemeData _getExpandableTheme() {

+ 33 - 0
lib/ui/components/button_widget.dart

@@ -60,6 +60,11 @@ class ButtonWidget extends StatelessWidget {
   ///This should be set to true if the alert which uses this button needs to
   ///This should be set to true if the alert which uses this button needs to
   ///return the Button's action.
   ///return the Button's action.
   final bool isInAlert;
   final bool isInAlert;
+
+  /// progressStatus can be used to display information about the action
+  /// progress when ExecutionState is in Progress.
+  final ValueNotifier<String>? progressStatus;
+
   const ButtonWidget({
   const ButtonWidget({
     required this.buttonType,
     required this.buttonType,
     this.buttonSize = ButtonSize.large,
     this.buttonSize = ButtonSize.large,
@@ -72,6 +77,7 @@ class ButtonWidget extends StatelessWidget {
     this.isInAlert = false,
     this.isInAlert = false,
     this.iconColor,
     this.iconColor,
     this.shouldSurfaceExecutionStates = true,
     this.shouldSurfaceExecutionStates = true,
+    this.progressStatus,
     super.key,
     super.key,
   });
   });
 
 
@@ -137,6 +143,7 @@ class ButtonWidget extends StatelessWidget {
       icon: icon,
       icon: icon,
       buttonAction: buttonAction,
       buttonAction: buttonAction,
       shouldSurfaceExecutionStates: shouldSurfaceExecutionStates,
       shouldSurfaceExecutionStates: shouldSurfaceExecutionStates,
+      progressStatus: progressStatus,
     );
     );
   }
   }
 }
 }
@@ -152,6 +159,8 @@ class ButtonChildWidget extends StatefulWidget {
   final ButtonAction? buttonAction;
   final ButtonAction? buttonAction;
   final bool isInAlert;
   final bool isInAlert;
   final bool shouldSurfaceExecutionStates;
   final bool shouldSurfaceExecutionStates;
+  final ValueNotifier<String>? progressStatus;
+
   const ButtonChildWidget({
   const ButtonChildWidget({
     required this.buttonStyle,
     required this.buttonStyle,
     required this.buttonType,
     required this.buttonType,
@@ -159,6 +168,7 @@ class ButtonChildWidget extends StatefulWidget {
     required this.buttonSize,
     required this.buttonSize,
     required this.isInAlert,
     required this.isInAlert,
     required this.shouldSurfaceExecutionStates,
     required this.shouldSurfaceExecutionStates,
+    this.progressStatus,
     this.onTap,
     this.onTap,
     this.labelText,
     this.labelText,
     this.icon,
     this.icon,
@@ -177,14 +187,17 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
   late TextStyle labelStyle;
   late TextStyle labelStyle;
   late Color checkIconColor;
   late Color checkIconColor;
   late Color loadingIconColor;
   late Color loadingIconColor;
+  ValueNotifier<String>? progressStatus;
 
 
   ///This is used to store the width of the button in idle state (small button)
   ///This is used to store the width of the button in idle state (small button)
   ///to be used as width for the button when the loading/succes states comes.
   ///to be used as width for the button when the loading/succes states comes.
   double? widthOfButton;
   double? widthOfButton;
   final _debouncer = Debouncer(const Duration(milliseconds: 300));
   final _debouncer = Debouncer(const Duration(milliseconds: 300));
   ExecutionState executionState = ExecutionState.idle;
   ExecutionState executionState = ExecutionState.idle;
+
   @override
   @override
   void initState() {
   void initState() {
+    progressStatus = widget.progressStatus;
     checkIconColor = widget.buttonStyle.checkIconColor ??
     checkIconColor = widget.buttonStyle.checkIconColor ??
         widget.buttonStyle.defaultIconColor;
         widget.buttonStyle.defaultIconColor;
     loadingIconColor = widget.buttonStyle.defaultIconColor;
     loadingIconColor = widget.buttonStyle.defaultIconColor;
@@ -203,6 +216,7 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
       iconColor = widget.buttonStyle.defaultIconColor;
       iconColor = widget.buttonStyle.defaultIconColor;
       labelStyle = widget.buttonStyle.defaultLabelStyle;
       labelStyle = widget.buttonStyle.defaultLabelStyle;
     }
     }
+
     super.initState();
     super.initState();
   }
   }
 
 
@@ -315,6 +329,25 @@ class _ButtonChildWidgetState extends State<ButtonChildWidget> {
                             mainAxisAlignment: MainAxisAlignment.center,
                             mainAxisAlignment: MainAxisAlignment.center,
                             mainAxisSize: MainAxisSize.min,
                             mainAxisSize: MainAxisSize.min,
                             children: [
                             children: [
+                              progressStatus == null
+                                  ? const SizedBox.shrink()
+                                  : ValueListenableBuilder<String>(
+                                      valueListenable: progressStatus!,
+                                      builder: (
+                                        BuildContext context,
+                                        String value,
+                                        Widget? child,
+                                      ) {
+                                        return Padding(
+                                          padding:
+                                              const EdgeInsets.only(right: 8.0),
+                                          child: Text(
+                                            value,
+                                            style: lightTextTheme.smallBold,
+                                          ),
+                                        );
+                                      },
+                                    ),
                               EnteLoadingWidget(
                               EnteLoadingWidget(
                                 is20pts: true,
                                 is20pts: true,
                                 color: loadingIconColor,
                                 color: loadingIconColor,

+ 0 - 1
lib/ui/components/keyboard/keyboard_top_button.dart

@@ -1,5 +1,4 @@
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
 
 
 class KeyboardTopButton extends StatelessWidget {
 class KeyboardTopButton extends StatelessWidget {

+ 26 - 24
lib/ui/create_collection_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:collection/collection.dart';
 import 'package:collection/collection.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -46,14 +44,14 @@ String _actionName(CollectionActionType type, bool plural) {
 }
 }
 
 
 class CreateCollectionPage extends StatefulWidget {
 class CreateCollectionPage extends StatefulWidget {
-  final SelectedFiles selectedFiles;
-  final List<SharedMediaFile> sharedFiles;
+  final SelectedFiles? selectedFiles;
+  final List<SharedMediaFile>? sharedFiles;
   final CollectionActionType actionType;
   final CollectionActionType actionType;
 
 
   const CreateCollectionPage(
   const CreateCollectionPage(
     this.selectedFiles,
     this.selectedFiles,
     this.sharedFiles, {
     this.sharedFiles, {
-    Key key,
+    Key? key,
     this.actionType = CollectionActionType.addFiles,
     this.actionType = CollectionActionType.addFiles,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -63,13 +61,13 @@ class CreateCollectionPage extends StatefulWidget {
 
 
 class _CreateCollectionPageState extends State<CreateCollectionPage> {
 class _CreateCollectionPageState extends State<CreateCollectionPage> {
   final _logger = Logger((_CreateCollectionPageState).toString());
   final _logger = Logger((_CreateCollectionPageState).toString());
-  String _albumName;
+  late String _albumName;
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final filesCount = widget.sharedFiles != null
     final filesCount = widget.sharedFiles != null
-        ? widget.sharedFiles.length
-        : widget.selectedFiles.files.length;
+        ? widget.sharedFiles!.length
+        : widget.selectedFiles!.files.length;
     return Scaffold(
     return Scaffold(
       appBar: AppBar(
       appBar: AppBar(
         title: Text(_actionName(widget.actionType, filesCount > 1)),
         title: Text(_actionName(widget.actionType, filesCount > 1)),
@@ -134,9 +132,9 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
         } else if (snapshot.hasData) {
         } else if (snapshot.hasData) {
           return ListView.builder(
           return ListView.builder(
             itemBuilder: (context, index) {
             itemBuilder: (context, index) {
-              return _buildCollectionItem(snapshot.data[index]);
+              return _buildCollectionItem(snapshot.data![index]);
             },
             },
-            itemCount: snapshot.data.length,
+            itemCount: snapshot.data!.length,
             shrinkWrap: true,
             shrinkWrap: true,
             physics: const NeverScrollableScrollPhysics(),
             physics: const NeverScrollableScrollPhysics(),
           );
           );
@@ -171,7 +169,7 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
             const Padding(padding: EdgeInsets.all(8)),
             const Padding(padding: EdgeInsets.all(8)),
             Expanded(
             Expanded(
               child: Text(
               child: Text(
-                item.collection.name,
+                item.collection.name!,
                 style: const TextStyle(
                 style: const TextStyle(
                   fontSize: 16,
                   fontSize: 16,
                 ),
                 ),
@@ -184,8 +182,8 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
             showShortToast(
             showShortToast(
               context,
               context,
               widget.actionType == CollectionActionType.addFiles
               widget.actionType == CollectionActionType.addFiles
-                  ? "Added successfully to " + item.collection.name
-                  : "Moved successfully to " + item.collection.name,
+                  ? "Added successfully to " + item.collection.name!
+                  : "Moved successfully to " + item.collection.name!,
             );
             );
             _navigateToCollection(item.collection);
             _navigateToCollection(item.collection);
           }
           }
@@ -294,7 +292,6 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
       case CollectionActionType.restoreFiles:
       case CollectionActionType.restoreFiles:
         return _restoreFilesToCollection(collectionID);
         return _restoreFilesToCollection(collectionID);
     }
     }
-    throw AssertionError("unexpected actionType ${widget.actionType}");
   }
   }
 
 
   Future<bool> _moveFilesToCollection(int toCollectionID) async {
   Future<bool> _moveFilesToCollection(int toCollectionID) async {
@@ -305,11 +302,11 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     await dialog.show();
     await dialog.show();
     try {
     try {
       final int fromCollectionID =
       final int fromCollectionID =
-          widget.selectedFiles.files.first?.collectionID;
+          widget.selectedFiles!.files.first.collectionID!;
       await CollectionsService.instance.move(
       await CollectionsService.instance.move(
         toCollectionID,
         toCollectionID,
         fromCollectionID,
         fromCollectionID,
-        widget.selectedFiles.files?.toList(),
+        widget.selectedFiles!.files.toList(),
       );
       );
       await dialog.hide();
       await dialog.hide();
       RemoteSyncService.instance.sync(silently: true);
       RemoteSyncService.instance.sync(silently: true);
@@ -318,7 +315,7 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
       return true;
       return true;
     } on AssertionError catch (e) {
     } on AssertionError catch (e) {
       await dialog.hide();
       await dialog.hide();
-      showErrorDialog(context, "Oops", e.message);
+      showErrorDialog(context, "Oops", e.message as String?);
       return false;
       return false;
     } catch (e, s) {
     } catch (e, s) {
       _logger.severe("Could not move to album", e, s);
       _logger.severe("Could not move to album", e, s);
@@ -333,14 +330,14 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     await dialog.show();
     await dialog.show();
     try {
     try {
       await CollectionsService.instance
       await CollectionsService.instance
-          .restore(toCollectionID, widget.selectedFiles.files?.toList());
+          .restore(toCollectionID, widget.selectedFiles!.files.toList());
       RemoteSyncService.instance.sync(silently: true);
       RemoteSyncService.instance.sync(silently: true);
       widget.selectedFiles?.clearAll();
       widget.selectedFiles?.clearAll();
       await dialog.hide();
       await dialog.hide();
       return true;
       return true;
     } on AssertionError catch (e) {
     } on AssertionError catch (e) {
       await dialog.hide();
       await dialog.hide();
-      showErrorDialog(context, "Oops", e.message);
+      showErrorDialog(context, "Oops", e.message as String?);
       return false;
       return false;
     } catch (e, s) {
     } catch (e, s) {
       _logger.severe("Could not move to album", e, s);
       _logger.severe("Could not move to album", e, s);
@@ -359,13 +356,18 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
       if (widget.sharedFiles != null) {
       if (widget.sharedFiles != null) {
         filesPendingUpload.addAll(
         filesPendingUpload.addAll(
           await convertIncomingSharedMediaToFile(
           await convertIncomingSharedMediaToFile(
-            widget.sharedFiles,
+            widget.sharedFiles!,
             collectionID,
             collectionID,
           ),
           ),
         );
         );
       } else {
       } else {
-        for (final file in widget.selectedFiles.files) {
-          final currentFile = await FilesDB.instance.getFile(file.generatedID);
+        for (final file in widget.selectedFiles!.files) {
+          final File? currentFile =
+              await (FilesDB.instance.getFile(file.generatedID!));
+          if (currentFile == null) {
+            _logger.severe("Failed to find fileBy genID");
+            continue;
+          }
           if (currentFile.uploadedFileID == null) {
           if (currentFile.uploadedFileID == null) {
             currentFile.collectionID = collectionID;
             currentFile.collectionID = collectionID;
             filesPendingUpload.add(currentFile);
             filesPendingUpload.add(currentFile);
@@ -396,8 +398,8 @@ class _CreateCollectionPageState extends State<CreateCollectionPage> {
     return false;
     return false;
   }
   }
 
 
-  Future<Collection> _createAlbum(String albumName) async {
-    Collection collection;
+  Future<Collection?> _createAlbum(String albumName) async {
+    Collection? collection;
     final dialog = createProgressDialog(context, "Creating album...");
     final dialog = createProgressDialog(context, "Creating album...");
     await dialog.show();
     await dialog.show();
     try {
     try {

+ 24 - 28
lib/ui/extents_page_view.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'package:flutter/gestures.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/rendering.dart';
 import 'package:flutter/widgets.dart' hide PageView;
 import 'package:flutter/widgets.dart' hide PageView;
@@ -49,10 +47,10 @@ class ExtentsPageView extends StatefulWidget {
   /// child that could possibly be displayed in the page view, instead of just
   /// child that could possibly be displayed in the page view, instead of just
   /// those children that are actually visible.
   /// those children that are actually visible.
   ExtentsPageView({
   ExtentsPageView({
-    Key key,
+    Key? key,
     this.scrollDirection = Axis.horizontal,
     this.scrollDirection = Axis.horizontal,
     this.reverse = false,
     this.reverse = false,
-    PageController controller,
+    PageController? controller,
     this.physics,
     this.physics,
     this.pageSnapping = true,
     this.pageSnapping = true,
     this.onPageChanged,
     this.onPageChanged,
@@ -81,15 +79,15 @@ class ExtentsPageView extends StatefulWidget {
   /// you are planning to change child order at a later time, consider using
   /// you are planning to change child order at a later time, consider using
   /// [PageView] or [PageView.custom].
   /// [PageView] or [PageView.custom].
   ExtentsPageView.builder({
   ExtentsPageView.builder({
-    Key key,
+    Key? key,
     this.scrollDirection = Axis.horizontal,
     this.scrollDirection = Axis.horizontal,
     this.reverse = false,
     this.reverse = false,
-    PageController controller,
+    PageController? controller,
     this.physics,
     this.physics,
     this.pageSnapping = true,
     this.pageSnapping = true,
     this.onPageChanged,
     this.onPageChanged,
-    @required IndexedWidgetBuilder itemBuilder,
-    int itemCount,
+    required IndexedWidgetBuilder itemBuilder,
+    int? itemCount,
     this.dragStartBehavior = DragStartBehavior.start,
     this.dragStartBehavior = DragStartBehavior.start,
     this.openDrawer,
     this.openDrawer,
   })  : controller = controller ?? _defaultPageController,
   })  : controller = controller ?? _defaultPageController,
@@ -99,16 +97,16 @@ class ExtentsPageView extends StatefulWidget {
         super(key: key);
         super(key: key);
 
 
   ExtentsPageView.extents({
   ExtentsPageView.extents({
-    Key key,
+    Key? key,
     this.extents = 1,
     this.extents = 1,
     this.scrollDirection = Axis.horizontal,
     this.scrollDirection = Axis.horizontal,
     this.reverse = false,
     this.reverse = false,
-    PageController controller,
+    PageController? controller,
     this.physics,
     this.physics,
     this.pageSnapping = true,
     this.pageSnapping = true,
     this.onPageChanged,
     this.onPageChanged,
-    @required IndexedWidgetBuilder itemBuilder,
-    int itemCount,
+    required IndexedWidgetBuilder itemBuilder,
+    int? itemCount,
     this.dragStartBehavior = DragStartBehavior.start,
     this.dragStartBehavior = DragStartBehavior.start,
     this.openDrawer,
     this.openDrawer,
   })  : controller = controller ?? _defaultPageController,
   })  : controller = controller ?? _defaultPageController,
@@ -201,18 +199,17 @@ class ExtentsPageView extends StatefulWidget {
   /// ```
   /// ```
   /// {@end-tool}
   /// {@end-tool}
   ExtentsPageView.custom({
   ExtentsPageView.custom({
-    Key key,
+    Key? key,
     this.scrollDirection = Axis.horizontal,
     this.scrollDirection = Axis.horizontal,
     this.reverse = false,
     this.reverse = false,
-    PageController controller,
+    PageController? controller,
     this.physics,
     this.physics,
     this.pageSnapping = true,
     this.pageSnapping = true,
     this.onPageChanged,
     this.onPageChanged,
-    @required this.childrenDelegate,
+    required this.childrenDelegate,
     this.dragStartBehavior = DragStartBehavior.start,
     this.dragStartBehavior = DragStartBehavior.start,
     this.openDrawer,
     this.openDrawer,
-  })  : assert(childrenDelegate != null),
-        extents = 0,
+  })  : extents = 0,
         controller = controller ?? _defaultPageController,
         controller = controller ?? _defaultPageController,
         super(key: key);
         super(key: key);
 
 
@@ -257,13 +254,13 @@ class ExtentsPageView extends StatefulWidget {
   /// [PageScrollPhysics] prior to being used.
   /// [PageScrollPhysics] prior to being used.
   ///
   ///
   /// Defaults to matching platform conventions.
   /// Defaults to matching platform conventions.
-  final ScrollPhysics physics;
+  final ScrollPhysics? physics;
 
 
   /// Set to false to disable page snapping, useful for custom scroll behavior.
   /// Set to false to disable page snapping, useful for custom scroll behavior.
   final bool pageSnapping;
   final bool pageSnapping;
 
 
   /// Called whenever the page in the center of the viewport changes.
   /// Called whenever the page in the center of the viewport changes.
-  final ValueChanged<int> onPageChanged;
+  final ValueChanged<int>? onPageChanged;
 
 
   /// A delegate that provides the children for the [PageView].
   /// A delegate that provides the children for the [PageView].
   ///
   ///
@@ -276,7 +273,7 @@ class ExtentsPageView extends StatefulWidget {
   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
   /// {@macro flutter.widgets.scrollable.dragStartBehavior}
   final DragStartBehavior dragStartBehavior;
   final DragStartBehavior dragStartBehavior;
 
 
-  final Function openDrawer; //nullable
+  final Function? openDrawer; //nullable
 
 
   @override
   @override
   State<ExtentsPageView> createState() => _PageViewState();
   State<ExtentsPageView> createState() => _PageViewState();
@@ -292,7 +289,7 @@ class _PageViewState extends State<ExtentsPageView> {
     widget.openDrawer != null
     widget.openDrawer != null
         ? widget.controller.addListener(() {
         ? widget.controller.addListener(() {
             if (widget.controller.offset < -45) {
             if (widget.controller.offset < -45) {
-              widget.openDrawer();
+              widget.openDrawer!();
             }
             }
           })
           })
         : null;
         : null;
@@ -304,7 +301,7 @@ class _PageViewState extends State<ExtentsPageView> {
     super.dispose();
     super.dispose();
   }
   }
 
 
-  AxisDirection _getDirection(BuildContext context) {
+  AxisDirection? _getDirection(BuildContext context) {
     switch (widget.scrollDirection) {
     switch (widget.scrollDirection) {
       case Axis.horizontal:
       case Axis.horizontal:
         assert(debugCheckHasDirectionality(context));
         assert(debugCheckHasDirectionality(context));
@@ -317,13 +314,12 @@ class _PageViewState extends State<ExtentsPageView> {
       case Axis.vertical:
       case Axis.vertical:
         return widget.reverse ? AxisDirection.up : AxisDirection.down;
         return widget.reverse ? AxisDirection.up : AxisDirection.down;
     }
     }
-    return null;
   }
   }
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final AxisDirection axisDirection = _getDirection(context);
-    final ScrollPhysics physics = widget.pageSnapping
+    final AxisDirection axisDirection = _getDirection(context)!;
+    final ScrollPhysics? physics = widget.pageSnapping
         ? _kPagePhysics.applyTo(widget.physics)
         ? _kPagePhysics.applyTo(widget.physics)
         : widget.physics;
         : widget.physics;
 
 
@@ -332,11 +328,11 @@ class _PageViewState extends State<ExtentsPageView> {
         if (notification.depth == 0 &&
         if (notification.depth == 0 &&
             widget.onPageChanged != null &&
             widget.onPageChanged != null &&
             notification is ScrollUpdateNotification) {
             notification is ScrollUpdateNotification) {
-          final PageMetrics metrics = notification.metrics;
-          final int currentPage = metrics.page.round();
+          final PageMetrics metrics = notification.metrics as PageMetrics;
+          final int currentPage = metrics.page!.round();
           if (currentPage != _lastReportedPage) {
           if (currentPage != _lastReportedPage) {
             _lastReportedPage = currentPage;
             _lastReportedPage = currentPage;
-            widget.onPageChanged(currentPage);
+            widget.onPageChanged!(currentPage);
           }
           }
         }
         }
         return false;
         return false;

+ 4 - 4
lib/ui/home/header_error_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/core/errors.dart';
 import 'package:photos/core/errors.dart';
@@ -7,9 +7,9 @@ import 'package:photos/ui/payment/subscription.dart';
 import 'package:photos/utils/email_util.dart';
 import 'package:photos/utils/email_util.dart';
 
 
 class HeaderErrorWidget extends StatelessWidget {
 class HeaderErrorWidget extends StatelessWidget {
-  final Error _error;
+  final Error? _error;
 
 
-  const HeaderErrorWidget({Key key, @required Error error})
+  const HeaderErrorWidget({Key? key, required Error? error})
       : _error = error,
       : _error = error,
         super(key: key);
         super(key: key);
 
 
@@ -123,7 +123,7 @@ class HeaderErrorWidget extends StatelessWidget {
                   padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
                   padding: const EdgeInsets.fromLTRB(50, 16, 50, 16),
                   side: BorderSide(
                   side: BorderSide(
                     width: 2,
                     width: 2,
-                    color: Colors.orange[600],
+                    color: Colors.orange[600]!,
                   ),
                   ),
                 ),
                 ),
                 child: Text(
                 child: Text(

+ 0 - 1
lib/ui/home/home_bottom_nav_bar.dart

@@ -2,7 +2,6 @@ import 'dart:async';
 import 'dart:ui';
 import 'dart:ui';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/core/event_bus.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/events/tab_changed_event.dart';
 import 'package:photos/events/tab_changed_event.dart';

+ 6 - 7
lib/ui/home/home_gallery_widget.dart

@@ -1,4 +1,3 @@
-// @dart=2.9
 import 'package:flutter/material.dart';
 import 'package:flutter/material.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';
@@ -16,15 +15,15 @@ import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 import 'package:photos/ui/viewer/gallery/gallery.dart';
 
 
 class HomeGalleryWidget extends StatelessWidget {
 class HomeGalleryWidget extends StatelessWidget {
-  final Widget header;
-  final Widget footer;
+  final Widget? header;
+  final Widget? footer;
   final SelectedFiles selectedFiles;
   final SelectedFiles selectedFiles;
 
 
   const HomeGalleryWidget({
   const HomeGalleryWidget({
-    Key key,
+    Key? key,
     this.header,
     this.header,
     this.footer,
     this.footer,
-    this.selectedFiles,
+    required this.selectedFiles,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -42,7 +41,7 @@ class HomeGalleryWidget extends StatelessWidget {
           result = await FilesDB.instance.getAllLocalAndUploadedFiles(
           result = await FilesDB.instance.getAllLocalAndUploadedFiles(
             creationStartTime,
             creationStartTime,
             creationEndTime,
             creationEndTime,
-            ownerID,
+            ownerID!,
             limit: limit,
             limit: limit,
             asc: asc,
             asc: asc,
             ignoredCollectionIDs: collectionsToHide,
             ignoredCollectionIDs: collectionsToHide,
@@ -51,7 +50,7 @@ class HomeGalleryWidget extends StatelessWidget {
           result = await FilesDB.instance.getAllPendingOrUploadedFiles(
           result = await FilesDB.instance.getAllPendingOrUploadedFiles(
             creationStartTime,
             creationStartTime,
             creationEndTime,
             creationEndTime,
-            ownerID,
+            ownerID!,
             limit: limit,
             limit: limit,
             asc: asc,
             asc: asc,
             ignoredCollectionIDs: collectionsToHide,
             ignoredCollectionIDs: collectionsToHide,

+ 16 - 16
lib/ui/home/memories_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
@@ -14,20 +14,20 @@ import 'package:photos/utils/share_util.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 import 'package:step_progress_indicator/step_progress_indicator.dart';
 
 
 class MemoriesWidget extends StatelessWidget {
 class MemoriesWidget extends StatelessWidget {
-  const MemoriesWidget({Key key}) : super(key: key);
+  const MemoriesWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return FutureBuilder<List<Memory>>(
     return FutureBuilder<List<Memory>>(
       future: MemoriesService.instance.getMemories(),
       future: MemoriesService.instance.getMemories(),
       builder: (context, snapshot) {
       builder: (context, snapshot) {
-        if (snapshot.hasError || !snapshot.hasData || snapshot.data.isEmpty) {
+        if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
           return const SizedBox.shrink();
           return const SizedBox.shrink();
         } else {
         } else {
           return Column(
           return Column(
             crossAxisAlignment: CrossAxisAlignment.start,
             crossAxisAlignment: CrossAxisAlignment.start,
             children: [
             children: [
-              _buildMemories(snapshot.data),
+              _buildMemories(snapshot.data!),
               const Divider(),
               const Divider(),
             ],
             ],
           );
           );
@@ -69,17 +69,17 @@ class MemoriesWidget extends StatelessWidget {
 
 
   bool _areMemoriesFromSameYear(Memory first, Memory second) {
   bool _areMemoriesFromSameYear(Memory first, Memory second) {
     final firstDate =
     final firstDate =
-        DateTime.fromMicrosecondsSinceEpoch(first.file.creationTime);
+        DateTime.fromMicrosecondsSinceEpoch(first.file.creationTime!);
     final secondDate =
     final secondDate =
-        DateTime.fromMicrosecondsSinceEpoch(second.file.creationTime);
+        DateTime.fromMicrosecondsSinceEpoch(second.file.creationTime!);
     return firstDate.year == secondDate.year;
     return firstDate.year == secondDate.year;
   }
   }
 }
 }
 
 
 class MemoryWidget extends StatefulWidget {
 class MemoryWidget extends StatefulWidget {
   const MemoryWidget({
   const MemoryWidget({
-    Key key,
-    @required this.memories,
+    Key? key,
+    required this.memories,
   }) : super(key: key);
   }) : super(key: key);
 
 
   final List<Memory> memories;
   final List<Memory> memories;
@@ -119,7 +119,7 @@ class _MemoryWidgetState extends State<MemoryWidget> {
                     title,
                     title,
                     style: Theme.of(context)
                     style: Theme.of(context)
                         .textTheme
                         .textTheme
-                        .subtitle1
+                        .subtitle1!
                         .copyWith(fontSize: 12),
                         .copyWith(fontSize: 12),
                     textAlign: TextAlign.center,
                     textAlign: TextAlign.center,
                   ),
                   ),
@@ -186,7 +186,7 @@ class _MemoryWidgetState extends State<MemoryWidget> {
 
 
   String _getTitle(Memory memory) {
   String _getTitle(Memory memory) {
     final present = DateTime.now();
     final present = DateTime.now();
-    final then = DateTime.fromMicrosecondsSinceEpoch(memory.file.creationTime);
+    final then = DateTime.fromMicrosecondsSinceEpoch(memory.file.creationTime!);
     final diffInYears = present.year - then.year;
     final diffInYears = present.year - then.year;
     if (diffInYears == 1) {
     if (diffInYears == 1) {
       return "1 year ago";
       return "1 year ago";
@@ -201,7 +201,7 @@ class FullScreenMemory extends StatefulWidget {
   final List<Memory> memories;
   final List<Memory> memories;
   final int index;
   final int index;
 
 
-  const FullScreenMemory(this.title, this.memories, this.index, {Key key})
+  const FullScreenMemory(this.title, this.memories, this.index, {Key? key})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override
@@ -215,7 +215,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
   // when the top step indicator isn't visible.
   // when the top step indicator isn't visible.
   bool _showCounter = false;
   bool _showCounter = false;
   bool _showStepIndicator = true;
   bool _showStepIndicator = true;
-  PageController _pageController;
+  PageController? _pageController;
   bool _shouldDisableScroll = false;
   bool _shouldDisableScroll = false;
   final GlobalKey shareButtonKey = GlobalKey();
   final GlobalKey shareButtonKey = GlobalKey();
 
 
@@ -273,9 +273,9 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
                 ),
                 ),
                 Text(
                 Text(
                   getFormattedDate(
                   getFormattedDate(
-                    DateTime.fromMicrosecondsSinceEpoch(file.creationTime),
+                    DateTime.fromMicrosecondsSinceEpoch(file.creationTime!),
                   ),
                   ),
-                  style: Theme.of(context).textTheme.subtitle1.copyWith(
+                  style: Theme.of(context).textTheme.subtitle1!.copyWith(
                         fontSize: 14,
                         fontSize: 14,
                         color: Colors.white,
                         color: Colors.white,
                       ), //same for both themes
                       ), //same for both themes
@@ -328,7 +328,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
                 '${_index + 1}/${widget.memories.length}',
                 '${_index + 1}/${widget.memories.length}',
                 style: Theme.of(context)
                 style: Theme.of(context)
                     .textTheme
                     .textTheme
-                    .bodyText1
+                    .bodyText1!
                     .copyWith(color: Colors.white.withOpacity(0.4)),
                     .copyWith(color: Colors.white.withOpacity(0.4)),
               )
               )
             : AnimatedOpacity(
             : AnimatedOpacity(
@@ -338,7 +338,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
                   widget.title,
                   widget.title,
                   style: Theme.of(context)
                   style: Theme.of(context)
                       .textTheme
                       .textTheme
-                      .headline4
+                      .headline4!
                       .copyWith(color: Colors.white),
                       .copyWith(color: Colors.white),
                 ),
                 ),
               ),
               ),

+ 29 - 31
lib/ui/home/status_bar_widget.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -19,18 +17,18 @@ import 'package:photos/utils/navigation_util.dart';
 const double kContainerHeight = 36;
 const double kContainerHeight = 36;
 
 
 class StatusBarWidget extends StatefulWidget {
 class StatusBarWidget extends StatefulWidget {
-  const StatusBarWidget({Key key}) : super(key: key);
+  const StatusBarWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<StatusBarWidget> createState() => _StatusBarWidgetState();
   State<StatusBarWidget> createState() => _StatusBarWidgetState();
 }
 }
 
 
 class _StatusBarWidgetState extends State<StatusBarWidget> {
 class _StatusBarWidgetState extends State<StatusBarWidget> {
-  StreamSubscription<SyncStatusUpdate> _subscription;
-  StreamSubscription<NotificationEvent> _notificationSubscription;
+  late StreamSubscription<SyncStatusUpdate> _subscription;
+  late StreamSubscription<NotificationEvent> _notificationSubscription;
   bool _showStatus = false;
   bool _showStatus = false;
   bool _showErrorBanner = false;
   bool _showErrorBanner = false;
-  Error _syncError;
+  Error? _syncError;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -118,7 +116,7 @@ class _StatusBarWidgetState extends State<StatusBarWidget> {
 }
 }
 
 
 class SyncStatusWidget extends StatefulWidget {
 class SyncStatusWidget extends StatefulWidget {
-  const SyncStatusWidget({Key key}) : super(key: key);
+  const SyncStatusWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<SyncStatusWidget> createState() => _SyncStatusWidgetState();
   State<SyncStatusWidget> createState() => _SyncStatusWidgetState();
@@ -127,8 +125,8 @@ class SyncStatusWidget extends StatefulWidget {
 class _SyncStatusWidgetState extends State<SyncStatusWidget> {
 class _SyncStatusWidgetState extends State<SyncStatusWidget> {
   static const Duration kSleepDuration = Duration(milliseconds: 3000);
   static const Duration kSleepDuration = Duration(milliseconds: 3000);
 
 
-  SyncStatusUpdate _event;
-  StreamSubscription<SyncStatusUpdate> _subscription;
+  SyncStatusUpdate? _event;
+  late StreamSubscription<SyncStatusUpdate> _subscription;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -150,17 +148,17 @@ class _SyncStatusWidgetState extends State<SyncStatusWidget> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     final bool isNotOutdatedEvent = _event != null &&
     final bool isNotOutdatedEvent = _event != null &&
-        (_event.status == SyncStatus.completedBackup ||
-            _event.status == SyncStatus.completedFirstGalleryImport) &&
-        (DateTime.now().microsecondsSinceEpoch - _event.timestamp >
+        (_event!.status == SyncStatus.completedBackup ||
+            _event!.status == SyncStatus.completedFirstGalleryImport) &&
+        (DateTime.now().microsecondsSinceEpoch - _event!.timestamp >
             kSleepDuration.inMicroseconds);
             kSleepDuration.inMicroseconds);
     if (_event == null ||
     if (_event == null ||
         isNotOutdatedEvent ||
         isNotOutdatedEvent ||
         //sync error cases are handled in StatusBarWidget
         //sync error cases are handled in StatusBarWidget
-        _event.status == SyncStatus.error) {
+        _event!.status == SyncStatus.error) {
       return const SizedBox.shrink();
       return const SizedBox.shrink();
     }
     }
-    if (_event.status == SyncStatus.completedBackup) {
+    if (_event!.status == SyncStatus.completedBackup) {
       return const SyncStatusCompletedWidget();
       return const SyncStatusCompletedWidget();
     }
     }
     return RefreshIndicatorWidget(_event);
     return RefreshIndicatorWidget(_event);
@@ -173,9 +171,9 @@ class RefreshIndicatorWidget extends StatelessWidget {
     valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(45, 194, 98, 1.0)),
     valueColor: AlwaysStoppedAnimation<Color>(Color.fromRGBO(45, 194, 98, 1.0)),
   );
   );
 
 
-  final SyncStatusUpdate event;
+  final SyncStatusUpdate? event;
 
 
-  const RefreshIndicatorWidget(this.event, {Key key}) : super(key: key);
+  const RefreshIndicatorWidget(this.event, {Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -211,30 +209,30 @@ class RefreshIndicatorWidget extends StatelessWidget {
   }
   }
 
 
   String _getRefreshingText() {
   String _getRefreshingText() {
-    if (event.status == SyncStatus.startedFirstGalleryImport ||
-        event.status == SyncStatus.completedFirstGalleryImport) {
+    if (event!.status == SyncStatus.startedFirstGalleryImport ||
+        event!.status == SyncStatus.completedFirstGalleryImport) {
       return "Loading gallery...";
       return "Loading gallery...";
     }
     }
-    if (event.status == SyncStatus.applyingRemoteDiff) {
+    if (event!.status == SyncStatus.applyingRemoteDiff) {
       return "Syncing...";
       return "Syncing...";
     }
     }
-    if (event.status == SyncStatus.preparingForUpload) {
+    if (event!.status == SyncStatus.preparingForUpload) {
       return "Encrypting backup...";
       return "Encrypting backup...";
     }
     }
-    if (event.status == SyncStatus.inProgress) {
-      return event.completed.toString() +
+    if (event!.status == SyncStatus.inProgress) {
+      return event!.completed.toString() +
           "/" +
           "/" +
-          event.total.toString() +
+          event!.total.toString() +
           " memories preserved";
           " memories preserved";
     }
     }
-    if (event.status == SyncStatus.paused) {
-      return event.reason;
+    if (event!.status == SyncStatus.paused) {
+      return event!.reason;
     }
     }
-    if (event.status == SyncStatus.error) {
-      return event.reason ?? "Upload failed";
+    if (event!.status == SyncStatus.error) {
+      return event!.reason;
     }
     }
-    if (event.status == SyncStatus.completedBackup) {
-      if (event.wasStopped) {
+    if (event!.status == SyncStatus.completedBackup) {
+      if (event!.wasStopped) {
         return "Sync stopped";
         return "Sync stopped";
       }
       }
     }
     }
@@ -243,7 +241,7 @@ class RefreshIndicatorWidget extends StatelessWidget {
 }
 }
 
 
 class BrandingWidget extends StatelessWidget {
 class BrandingWidget extends StatelessWidget {
-  const BrandingWidget({Key key}) : super(key: key);
+  const BrandingWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -271,7 +269,7 @@ class BrandingWidget extends StatelessWidget {
 }
 }
 
 
 class SyncStatusCompletedWidget extends StatelessWidget {
 class SyncStatusCompletedWidget extends StatelessWidget {
-  const SyncStatusCompletedWidget({Key key}) : super(key: key);
+  const SyncStatusCompletedWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {

+ 18 - 18
lib/ui/home_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
@@ -51,7 +51,7 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart';
 import 'package:uni_links/uni_links.dart';
 import 'package:uni_links/uni_links.dart';
 
 
 class HomeWidget extends StatefulWidget {
 class HomeWidget extends StatefulWidget {
-  const HomeWidget({Key key}) : super(key: key);
+  const HomeWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<StatefulWidget> createState() => _HomeWidgetState();
   State<StatefulWidget> createState() => _HomeWidgetState();
@@ -74,17 +74,17 @@ class _HomeWidgetState extends State<HomeWidget> {
 
 
   // for receiving media files
   // for receiving media files
   // ignore: unused_field
   // ignore: unused_field
-  StreamSubscription _intentDataStreamSubscription;
-  List<SharedMediaFile> _sharedFiles;
+  StreamSubscription? _intentDataStreamSubscription;
+  List<SharedMediaFile>? _sharedFiles;
 
 
-  StreamSubscription<TabChangedEvent> _tabChangedEventSubscription;
-  StreamSubscription<SubscriptionPurchasedEvent> _subscriptionPurchaseEvent;
-  StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
-  StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
-  StreamSubscription<PermissionGrantedEvent> _permissionGrantedEvent;
-  StreamSubscription<SyncStatusUpdate> _firstImportEvent;
-  StreamSubscription<BackupFoldersUpdatedEvent> _backupFoldersUpdatedEvent;
-  StreamSubscription<AccountConfiguredEvent> _accountConfiguredEvent;
+  late StreamSubscription<TabChangedEvent> _tabChangedEventSubscription;
+  late StreamSubscription<SubscriptionPurchasedEvent> _subscriptionPurchaseEvent;
+  late StreamSubscription<TriggerLogoutEvent> _triggerLogoutEvent;
+  late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
+  late StreamSubscription<PermissionGrantedEvent> _permissionGrantedEvent;
+  late StreamSubscription<SyncStatusUpdate> _firstImportEvent;
+  late StreamSubscription<BackupFoldersUpdatedEvent> _backupFoldersUpdatedEvent;
+  late StreamSubscription<AccountConfiguredEvent> _accountConfiguredEvent;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -323,7 +323,7 @@ class _HomeWidgetState extends State<HomeWidget> {
     if (UserRemoteFlagService.instance.showPasswordReminder()) {
     if (UserRemoteFlagService.instance.showPasswordReminder()) {
       return const PasswordReminder();
       return const PasswordReminder();
     }
     }
-    if (_sharedFiles != null && _sharedFiles.isNotEmpty) {
+    if (_sharedFiles != null && _sharedFiles!.isNotEmpty) {
       ReceiveSharingIntent.reset();
       ReceiveSharingIntent.reset();
       return CreateCollectionPage(null, _sharedFiles);
       return CreateCollectionPage(null, _sharedFiles);
     }
     }
@@ -392,7 +392,7 @@ class _HomeWidgetState extends State<HomeWidget> {
   Future<bool> _initDeepLinks() async {
   Future<bool> _initDeepLinks() async {
     // Platform messages may fail, so we use a try/catch PlatformException.
     // Platform messages may fail, so we use a try/catch PlatformException.
     try {
     try {
-      final String initialLink = await getInitialLink();
+      final String? initialLink = await getInitialLink();
       // Parse the link and warn the user, if it is not correct,
       // Parse the link and warn the user, if it is not correct,
       // but keep in mind it could be `null`.
       // but keep in mind it could be `null`.
       if (initialLink != null) {
       if (initialLink != null) {
@@ -410,8 +410,8 @@ class _HomeWidgetState extends State<HomeWidget> {
 
 
     // Attach a listener to the stream
     // Attach a listener to the stream
     linkStream.listen(
     linkStream.listen(
-      (String link) {
-        _logger.info("Link received: " + link);
+      (String? link) {
+        _logger.info("Link received: " + link!);
         _getCredentials(context, link);
         _getCredentials(context, link);
       },
       },
       onError: (err) {
       onError: (err) {
@@ -421,11 +421,11 @@ class _HomeWidgetState extends State<HomeWidget> {
     return false;
     return false;
   }
   }
 
 
-  void _getCredentials(BuildContext context, String link) {
+  void _getCredentials(BuildContext context, String? link) {
     if (Configuration.instance.hasConfiguredAccount()) {
     if (Configuration.instance.hasConfiguredAccount()) {
       return;
       return;
     }
     }
-    final ott = Uri.parse(link).queryParameters["ott"];
+    final ott = Uri.parse(link!).queryParameters["ott"]!;
     UserService.instance.verifyEmail(context, ott);
     UserService.instance.verifyEmail(context, ott);
   }
   }
 
 

+ 14 - 16
lib/ui/huge_listview/draggable_scrollbar.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
@@ -12,18 +10,18 @@ class DraggableScrollbar extends StatefulWidget {
   final Color backgroundColor;
   final Color backgroundColor;
   final Color drawColor;
   final Color drawColor;
   final double heightScrollThumb;
   final double heightScrollThumb;
-  final EdgeInsetsGeometry padding;
+  final EdgeInsetsGeometry? padding;
   final int totalCount;
   final int totalCount;
   final int initialScrollIndex;
   final int initialScrollIndex;
   final double bottomSafeArea;
   final double bottomSafeArea;
   final int currentFirstIndex;
   final int currentFirstIndex;
-  final ValueChanged<double> onChange;
+  final ValueChanged<double>? onChange;
   final String Function(int) labelTextBuilder;
   final String Function(int) labelTextBuilder;
   final bool isEnabled;
   final bool isEnabled;
 
 
   const DraggableScrollbar({
   const DraggableScrollbar({
-    Key key,
-    @required this.child,
+    Key? key,
+    required this.child,
     this.backgroundColor = Colors.white,
     this.backgroundColor = Colors.white,
     this.drawColor = Colors.grey,
     this.drawColor = Colors.grey,
     this.heightScrollThumb = 80.0,
     this.heightScrollThumb = 80.0,
@@ -32,7 +30,7 @@ class DraggableScrollbar extends StatefulWidget {
     this.totalCount = 1,
     this.totalCount = 1,
     this.initialScrollIndex = 0,
     this.initialScrollIndex = 0,
     this.currentFirstIndex = 0,
     this.currentFirstIndex = 0,
-    @required this.labelTextBuilder,
+    required this.labelTextBuilder,
     this.onChange,
     this.onChange,
     this.isEnabled = true,
     this.isEnabled = true,
   }) : super(key: key);
   }) : super(key: key);
@@ -47,18 +45,18 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
   static const labelAnimationDuration = Duration(milliseconds: 1000);
   static const labelAnimationDuration = Duration(milliseconds: 1000);
   double thumbOffset = 0.0;
   double thumbOffset = 0.0;
   bool isDragging = false;
   bool isDragging = false;
-  int currentFirstIndex;
+  late int currentFirstIndex;
 
 
   double get thumbMin => 0.0;
   double get thumbMin => 0.0;
 
 
   double get thumbMax =>
   double get thumbMax =>
-      context.size.height - widget.heightScrollThumb - widget.bottomSafeArea;
+      context.size!.height - widget.heightScrollThumb - widget.bottomSafeArea;
 
 
-  AnimationController _thumbAnimationController;
-  Animation<double> _thumbAnimation;
-  AnimationController _labelAnimationController;
-  Animation<double> _labelAnimation;
-  Timer _fadeoutTimer;
+  late AnimationController _thumbAnimationController;
+  Animation<double>? _thumbAnimation;
+  late AnimationController _labelAnimationController;
+  Animation<double>? _labelAnimation;
+  Timer? _fadeoutTimer;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -66,7 +64,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
     currentFirstIndex = widget.currentFirstIndex;
     currentFirstIndex = widget.currentFirstIndex;
 
 
     if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
     if (widget.initialScrollIndex > 0 && widget.totalCount > 1) {
-      WidgetsBinding.instance?.addPostFrameCallback((_) {
+      WidgetsBinding.instance.addPostFrameCallback((_) {
         setState(
         setState(
           () => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
           () => thumbOffset = (widget.initialScrollIndex / widget.totalCount) *
               (thumbMax - thumbMin),
               (thumbMax - thumbMin),
@@ -130,7 +128,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
   }
   }
 
 
   Widget buildThumb() => Padding(
   Widget buildThumb() => Padding(
-        padding: widget.padding,
+        padding: widget.padding!,
         child: Container(
         child: Container(
           alignment: Alignment.topRight,
           alignment: Alignment.topRight,
           margin: EdgeInsets.only(top: thumbOffset),
           margin: EdgeInsets.only(top: thumbOffset),

+ 15 - 15
lib/ui/huge_listview/huge_listview.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:math' show max;
 import 'dart:math' show max;
 
 
@@ -17,7 +17,7 @@ typedef HugeListViewErrorBuilder = Widget Function(
 
 
 class HugeListView<T> extends StatefulWidget {
 class HugeListView<T> extends StatefulWidget {
   /// A [ScrollablePositionedList] controller for jumping or scrolling to an item.
   /// A [ScrollablePositionedList] controller for jumping or scrolling to an item.
-  final ItemScrollController controller;
+  final ItemScrollController? controller;
 
 
   /// Index of an item to initially align within the viewport.
   /// Index of an item to initially align within the viewport.
   final int startIndex;
   final int startIndex;
@@ -46,29 +46,29 @@ class HugeListView<T> extends StatefulWidget {
   final HugeListViewItemBuilder<T> itemBuilder;
   final HugeListViewItemBuilder<T> itemBuilder;
 
 
   /// Called to build a progress widget while the whole list is initialized.
   /// Called to build a progress widget while the whole list is initialized.
-  final WidgetBuilder waitBuilder;
+  final WidgetBuilder? waitBuilder;
 
 
   /// Called to build a widget when the list is empty.
   /// Called to build a widget when the list is empty.
-  final WidgetBuilder emptyResultBuilder;
+  final WidgetBuilder? emptyResultBuilder;
 
 
   /// Called to build a widget when there is an error.
   /// Called to build a widget when there is an error.
-  final HugeListViewErrorBuilder errorBuilder;
+  final HugeListViewErrorBuilder? errorBuilder;
 
 
   /// Event to call with the index of the topmost visible item in the viewport while scrolling.
   /// Event to call with the index of the topmost visible item in the viewport while scrolling.
   /// Can be used to display the current letter of an alphabetically sorted list, for instance.
   /// Can be used to display the current letter of an alphabetically sorted list, for instance.
-  final ValueChanged<int> firstShown;
+  final ValueChanged<int>? firstShown;
 
 
   final bool isDraggableScrollbarEnabled;
   final bool isDraggableScrollbarEnabled;
 
 
-  final EdgeInsetsGeometry thumbPadding;
+  final EdgeInsetsGeometry? thumbPadding;
 
 
   const HugeListView({
   const HugeListView({
-    Key key,
+    Key? key,
     this.controller,
     this.controller,
-    @required this.startIndex,
-    @required this.totalCount,
-    @required this.labelTextBuilder,
-    @required this.itemBuilder,
+    required this.startIndex,
+    required this.totalCount,
+    required this.labelTextBuilder,
+    required this.itemBuilder,
     this.waitBuilder,
     this.waitBuilder,
     this.emptyResultBuilder,
     this.emptyResultBuilder,
     this.errorBuilder,
     this.errorBuilder,
@@ -121,13 +121,13 @@ class HugeListViewState<T> extends State<HugeListView<T>> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     if (error != null && widget.errorBuilder != null) {
     if (error != null && widget.errorBuilder != null) {
-      return widget.errorBuilder(context, error);
+      return widget.errorBuilder!(context, error);
     }
     }
     if (widget.totalCount == -1 && widget.waitBuilder != null) {
     if (widget.totalCount == -1 && widget.waitBuilder != null) {
-      return widget.waitBuilder(context);
+      return widget.waitBuilder!(context);
     }
     }
     if (widget.totalCount == 0 && widget.emptyResultBuilder != null) {
     if (widget.totalCount == 0 && widget.emptyResultBuilder != null) {
-      return widget.emptyResultBuilder(context);
+      return widget.emptyResultBuilder!(context);
     }
     }
 
 
     return LayoutBuilder(
     return LayoutBuilder(

+ 39 - 40
lib/ui/huge_listview/lazy_loading_gallery.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:math';
 import 'dart:math';
 
 
@@ -27,14 +25,14 @@ import 'package:visibility_detector/visibility_detector.dart';
 class LazyLoadingGallery extends StatefulWidget {
 class LazyLoadingGallery extends StatefulWidget {
   final List<File> files;
   final List<File> files;
   final int index;
   final int index;
-  final Stream<FilesUpdatedEvent> reloadEvent;
+  final Stream<FilesUpdatedEvent>? reloadEvent;
   final Set<EventType> removalEventTypes;
   final Set<EventType> removalEventTypes;
   final GalleryLoader asyncLoader;
   final GalleryLoader asyncLoader;
   final SelectedFiles selectedFiles;
   final SelectedFiles selectedFiles;
   final String tag;
   final String tag;
-  final String logTag;
+  final String? logTag;
   final Stream<int> currentIndexStream;
   final Stream<int> currentIndexStream;
-  final int photoGirdSize;
+  final int? photoGirdSize;
 
 
   LazyLoadingGallery(
   LazyLoadingGallery(
     this.files,
     this.files,
@@ -47,7 +45,7 @@ class LazyLoadingGallery extends StatefulWidget {
     this.currentIndexStream, {
     this.currentIndexStream, {
     this.logTag = "",
     this.logTag = "",
     this.photoGirdSize = photoGridSizeDefault,
     this.photoGirdSize = photoGridSizeDefault,
-    Key key,
+    Key? key,
   }) : super(key: key ?? UniqueKey());
   }) : super(key: key ?? UniqueKey());
 
 
   @override
   @override
@@ -58,12 +56,12 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
   static const kRecycleLimit = 400;
   static const kRecycleLimit = 400;
   static const kNumberOfDaysToRenderBeforeAndAfter = 8;
   static const kNumberOfDaysToRenderBeforeAndAfter = 8;
 
 
-  Logger _logger;
+  late Logger _logger;
 
 
-  List<File> _files;
-  StreamSubscription<FilesUpdatedEvent> _reloadEventSubscription;
-  StreamSubscription<int> _currentIndexSubscription;
-  bool _shouldRender;
+  late List<File> _files;
+  late StreamSubscription<FilesUpdatedEvent> _reloadEventSubscription;
+  late StreamSubscription<int> _currentIndexSubscription;
+  bool? _shouldRender;
   final ValueNotifier<bool> _toggleSelectAllFromDay = ValueNotifier(false);
   final ValueNotifier<bool> _toggleSelectAllFromDay = ValueNotifier(false);
   final ValueNotifier<bool> _showSelectAllButton = ValueNotifier(false);
   final ValueNotifier<bool> _showSelectAllButton = ValueNotifier(false);
   final ValueNotifier<bool> _areAllFromDaySelected = ValueNotifier(false);
   final ValueNotifier<bool> _areAllFromDaySelected = ValueNotifier(false);
@@ -80,7 +78,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
     _logger = Logger("LazyLoading_${widget.logTag}");
     _logger = Logger("LazyLoading_${widget.logTag}");
     _shouldRender = true;
     _shouldRender = true;
     _files = widget.files;
     _files = widget.files;
-    _reloadEventSubscription = widget.reloadEvent.listen((e) => _onReload(e));
+    _reloadEventSubscription = widget.reloadEvent!.listen((e) => _onReload(e));
 
 
     _currentIndexSubscription =
     _currentIndexSubscription =
         widget.currentIndexStream.listen((currentIndex) {
         widget.currentIndexStream.listen((currentIndex) {
@@ -96,9 +94,9 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
 
 
   Future _onReload(FilesUpdatedEvent event) async {
   Future _onReload(FilesUpdatedEvent event) async {
     final galleryDate =
     final galleryDate =
-        DateTime.fromMicrosecondsSinceEpoch(_files[0].creationTime);
+        DateTime.fromMicrosecondsSinceEpoch(_files[0].creationTime!);
     final filesUpdatedThisDay = event.updatedFiles.where((file) {
     final filesUpdatedThisDay = event.updatedFiles.where((file) {
-      final fileDate = DateTime.fromMicrosecondsSinceEpoch(file.creationTime);
+      final fileDate = DateTime.fromMicrosecondsSinceEpoch(file.creationTime!);
       return fileDate.year == galleryDate.year &&
       return fileDate.year == galleryDate.year &&
           fileDate.month == galleryDate.month &&
           fileDate.month == galleryDate.month &&
           fileDate.day == galleryDate.day;
           fileDate.day == galleryDate.day;
@@ -125,8 +123,8 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
         }
         }
       } else if (widget.removalEventTypes.contains(event.type)) {
       } else if (widget.removalEventTypes.contains(event.type)) {
         // Files were removed
         // Files were removed
-        final generatedFileIDs = <int>{};
-        final uploadedFileIds = <int>{};
+        final generatedFileIDs = <int?>{};
+        final uploadedFileIds = <int?>{};
         for (final file in filesUpdatedThisDay) {
         for (final file in filesUpdatedThisDay) {
           if (file.generatedID != null) {
           if (file.generatedID != null) {
             generatedFileIDs.add(file.generatedID);
             generatedFileIDs.add(file.generatedID);
@@ -191,12 +189,12 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
           children: [
           children: [
             getDayWidget(
             getDayWidget(
               context,
               context,
-              _files[0].creationTime,
-              widget.photoGirdSize,
+              _files[0].creationTime!,
+              widget.photoGirdSize!,
             ),
             ),
             ValueListenableBuilder(
             ValueListenableBuilder(
               valueListenable: _showSelectAllButton,
               valueListenable: _showSelectAllButton,
-              builder: (context, value, _) {
+              builder: (context, dynamic value, _) {
                 return !value
                 return !value
                     ? const SizedBox.shrink()
                     ? const SizedBox.shrink()
                     : GestureDetector(
                     : GestureDetector(
@@ -206,7 +204,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
                           height: 44,
                           height: 44,
                           child: ValueListenableBuilder(
                           child: ValueListenableBuilder(
                             valueListenable: _areAllFromDaySelected,
                             valueListenable: _areAllFromDaySelected,
-                            builder: (context, value, _) {
+                            builder: (context, dynamic value, _) {
                               return value
                               return value
                                   ? const Icon(
                                   ? const Icon(
                                       Icons.check_circle,
                                       Icons.check_circle,
@@ -232,11 +230,11 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
             )
             )
           ],
           ],
         ),
         ),
-        _shouldRender
+        _shouldRender!
             ? _getGallery()
             ? _getGallery()
             : PlaceHolderWidget(
             : PlaceHolderWidget(
                 _files.length,
                 _files.length,
-                widget.photoGirdSize,
+                widget.photoGirdSize!,
               ),
               ),
       ],
       ],
     );
     );
@@ -244,7 +242,7 @@ class _LazyLoadingGalleryState extends State<LazyLoadingGallery> {
 
 
   Widget _getGallery() {
   Widget _getGallery() {
     final List<Widget> childGalleries = [];
     final List<Widget> childGalleries = [];
-    final subGalleryItemLimit = widget.photoGirdSize < photoGridSizeDefault
+    final subGalleryItemLimit = widget.photoGirdSize! < photoGridSizeDefault
         ? subGalleryLimitMin
         ? subGalleryLimitMin
         : subGalleryLimitDefault;
         : subGalleryLimitDefault;
     for (int index = 0; index < _files.length; index += subGalleryItemLimit) {
     for (int index = 0; index < _files.length; index += subGalleryItemLimit) {
@@ -289,7 +287,7 @@ class LazyLoadingGridView extends StatefulWidget {
   final bool shouldRecycle;
   final bool shouldRecycle;
   final ValueNotifier toggleSelectAllFromDay;
   final ValueNotifier toggleSelectAllFromDay;
   final ValueNotifier areAllFilesSelected;
   final ValueNotifier areAllFilesSelected;
-  final int photoGridSize;
+  final int? photoGridSize;
 
 
   LazyLoadingGridView(
   LazyLoadingGridView(
     this.tag,
     this.tag,
@@ -301,7 +299,7 @@ class LazyLoadingGridView extends StatefulWidget {
     this.toggleSelectAllFromDay,
     this.toggleSelectAllFromDay,
     this.areAllFilesSelected,
     this.areAllFilesSelected,
     this.photoGridSize, {
     this.photoGridSize, {
-    Key key,
+    Key? key,
   }) : super(key: key ?? UniqueKey());
   }) : super(key: key ?? UniqueKey());
 
 
   @override
   @override
@@ -309,9 +307,9 @@ class LazyLoadingGridView extends StatefulWidget {
 }
 }
 
 
 class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
 class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
-  bool _shouldRender;
-  int _currentUserID;
-  StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent;
+  bool? _shouldRender;
+  int? _currentUserID;
+  late StreamSubscription<ClearSelectionsEvent> _clearSelectionsEvent;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -365,25 +363,25 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
           });
           });
         }
         }
       },
       },
-      child: _shouldRender
+      child: _shouldRender!
           ? _getGridView()
           ? _getGridView()
-          : PlaceHolderWidget(widget.filesInDay.length, widget.photoGridSize),
+          : PlaceHolderWidget(widget.filesInDay.length, widget.photoGridSize!),
     );
     );
   }
   }
 
 
   Widget _getNonRecyclableView() {
   Widget _getNonRecyclableView() {
-    if (!_shouldRender) {
+    if (!_shouldRender!) {
       return VisibilityDetector(
       return VisibilityDetector(
         key: UniqueKey(),
         key: UniqueKey(),
         onVisibilityChanged: (visibility) {
         onVisibilityChanged: (visibility) {
-          if (mounted && visibility.visibleFraction > 0 && !_shouldRender) {
+          if (mounted && visibility.visibleFraction > 0 && !_shouldRender!) {
             setState(() {
             setState(() {
               _shouldRender = true;
               _shouldRender = true;
             });
             });
           }
           }
         },
         },
         child:
         child:
-            PlaceHolderWidget(widget.filesInDay.length, widget.photoGridSize),
+            PlaceHolderWidget(widget.filesInDay.length, widget.photoGridSize!),
       );
       );
     } else {
     } else {
       return _getGridView();
       return _getGridView();
@@ -402,7 +400,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
       gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
       gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
         crossAxisSpacing: 2,
         crossAxisSpacing: 2,
         mainAxisSpacing: 2,
         mainAxisSpacing: 2,
-        crossAxisCount: widget.photoGridSize,
+        crossAxisCount: widget.photoGridSize!,
       ),
       ),
       padding: const EdgeInsets.all(0),
       padding: const EdgeInsets.all(0),
     );
     );
@@ -414,11 +412,11 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
     if (isFileSelected &&
     if (isFileSelected &&
         file.isUploaded &&
         file.isUploaded &&
         (file.ownerID != _currentUserID ||
         (file.ownerID != _currentUserID ||
-            file.pubMagicMetadata.uploaderName != null)) {
+            file.pubMagicMetadata!.uploaderName != null)) {
       final avatarColors = getEnteColorScheme(context).avatarColors;
       final avatarColors = getEnteColorScheme(context).avatarColors;
       final int randomID = file.ownerID != _currentUserID
       final int randomID = file.ownerID != _currentUserID
-          ? file.ownerID
-          : file.pubMagicMetadata.uploaderName.sumAsciiValues;
+          ? file.ownerID!
+          : file.pubMagicMetadata!.uploaderName.sumAsciiValues;
       selectionColor = avatarColors[(randomID).remainder(avatarColors.length)];
       selectionColor = avatarColors[(randomID).remainder(avatarColors.length)];
     }
     }
     return GestureDetector(
     return GestureDetector(
@@ -452,7 +450,7 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
                   serverLoadDeferDuration: thumbnailServerLoadDeferDuration,
                   serverLoadDeferDuration: thumbnailServerLoadDeferDuration,
                   shouldShowLivePhotoOverlay: true,
                   shouldShowLivePhotoOverlay: true,
                   key: Key(widget.tag + file.tag),
                   key: Key(widget.tag + file.tag),
-                  thumbnailSize: widget.photoGridSize < photoGridSizeDefault
+                  thumbnailSize: widget.photoGridSize! < photoGridSizeDefault
                       ? thumbnailLargeSize
                       ? thumbnailLargeSize
                       : thumbnailSmallSize,
                       : thumbnailSmallSize,
                   shouldShowOwnerAvatar: !isFileSelected,
                   shouldShowOwnerAvatar: !isFileSelected,
@@ -513,10 +511,11 @@ class _LazyLoadingGridViewState extends State<LazyLoadingGridView> {
   void _toggleSelectAllFromDayListener() {
   void _toggleSelectAllFromDayListener() {
     if (widget.selectedFiles.files.containsAll(widget.filesInDay.toSet())) {
     if (widget.selectedFiles.files.containsAll(widget.filesInDay.toSet())) {
       setState(() {
       setState(() {
-        widget.selectedFiles.unSelectAll(widget.filesInDay.toSet());
+        widget.selectedFiles
+            .unSelectAll(widget.filesInDay.toSet() as Set<File>);
       });
       });
     } else {
     } else {
-      widget.selectedFiles.selectAll(widget.filesInDay.toSet());
+      widget.selectedFiles.selectAll(widget.filesInDay.toSet() as Set<File>);
     }
     }
   }
   }
 }
 }

+ 14 - 14
lib/ui/huge_listview/scroll_bar_thumb.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 
 
@@ -7,8 +7,8 @@ class ScrollBarThumb extends StatelessWidget {
   final Color drawColor;
   final Color drawColor;
   final double height;
   final double height;
   final String title;
   final String title;
-  final Animation labelAnimation;
-  final Animation thumbAnimation;
+  final Animation? labelAnimation;
+  final Animation? thumbAnimation;
   final Function(DragStartDetails details) onDragStart;
   final Function(DragStartDetails details) onDragStart;
   final Function(DragUpdateDetails details) onDragUpdate;
   final Function(DragUpdateDetails details) onDragUpdate;
   final Function(DragEndDetails details) onDragEnd;
   final Function(DragEndDetails details) onDragEnd;
@@ -23,7 +23,7 @@ class ScrollBarThumb extends StatelessWidget {
     this.onDragStart,
     this.onDragStart,
     this.onDragUpdate,
     this.onDragUpdate,
     this.onDragEnd, {
     this.onDragEnd, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -33,7 +33,7 @@ class ScrollBarThumb extends StatelessWidget {
       children: [
       children: [
         IgnorePointer(
         IgnorePointer(
           child: FadeTransition(
           child: FadeTransition(
-            opacity: labelAnimation,
+            opacity: labelAnimation as Animation<double>,
             child: Container(
             child: Container(
               padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
               padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
               decoration: BoxDecoration(
               decoration: BoxDecoration(
@@ -60,7 +60,7 @@ class ScrollBarThumb extends StatelessWidget {
           onVerticalDragUpdate: onDragUpdate,
           onVerticalDragUpdate: onDragUpdate,
           onVerticalDragEnd: onDragEnd,
           onVerticalDragEnd: onDragEnd,
           child: SlideFadeTransition(
           child: SlideFadeTransition(
-            animation: thumbAnimation,
+            animation: thumbAnimation as Animation<double>?,
             child: CustomPaint(
             child: CustomPaint(
               foregroundPainter: _ArrowCustomPainter(drawColor),
               foregroundPainter: _ArrowCustomPainter(drawColor),
               child: Material(
               child: Material(
@@ -131,27 +131,27 @@ class _ArrowCustomPainter extends CustomPainter {
 }
 }
 
 
 class SlideFadeTransition extends StatelessWidget {
 class SlideFadeTransition extends StatelessWidget {
-  final Animation<double> animation;
+  final Animation<double>? animation;
   final Widget child;
   final Widget child;
 
 
   const SlideFadeTransition({
   const SlideFadeTransition({
-    Key key,
-    @required this.animation,
-    @required this.child,
+    Key? key,
+    required this.animation,
+    required this.child,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
     return AnimatedBuilder(
     return AnimatedBuilder(
-      animation: animation,
-      builder: (context, child) => animation.value == 0.0 ? Container() : child,
+      animation: animation!,
+      builder: (context, child) => animation!.value == 0.0 ? Container() : child!,
       child: SlideTransition(
       child: SlideTransition(
         position: Tween(
         position: Tween(
           begin: const Offset(0.3, 0.0),
           begin: const Offset(0.3, 0.0),
           end: const Offset(0.0, 0.0),
           end: const Offset(0.0, 0.0),
-        ).animate(animation),
+        ).animate(animation!),
         child: FadeTransition(
         child: FadeTransition(
-          opacity: animation,
+          opacity: animation!,
           child: child,
           child: child,
         ),
         ),
       ),
       ),

+ 5 - 5
lib/ui/lifecycle_event_handler.dart

@@ -1,11 +1,11 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
 import 'package:flutter/widgets.dart';
 
 
 class LifecycleEventHandler extends WidgetsBindingObserver {
 class LifecycleEventHandler extends WidgetsBindingObserver {
-  final AsyncCallback resumeCallBack;
-  final AsyncCallback suspendingCallBack;
+  final AsyncCallback? resumeCallBack;
+  final AsyncCallback? suspendingCallBack;
 
 
   LifecycleEventHandler({
   LifecycleEventHandler({
     this.resumeCallBack,
     this.resumeCallBack,
@@ -17,14 +17,14 @@ class LifecycleEventHandler extends WidgetsBindingObserver {
     switch (state) {
     switch (state) {
       case AppLifecycleState.resumed:
       case AppLifecycleState.resumed:
         if (resumeCallBack != null) {
         if (resumeCallBack != null) {
-          await resumeCallBack();
+          await resumeCallBack!();
         }
         }
         break;
         break;
       case AppLifecycleState.inactive:
       case AppLifecycleState.inactive:
       case AppLifecycleState.paused:
       case AppLifecycleState.paused:
       case AppLifecycleState.detached:
       case AppLifecycleState.detached:
         if (suspendingCallBack != null) {
         if (suspendingCallBack != null) {
-          await suspendingCallBack();
+          await suspendingCallBack!();
         }
         }
         break;
         break;
     }
     }

+ 6 - 6
lib/ui/loading_photos_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
@@ -15,15 +15,15 @@ import 'package:photos/ui/common/bottom_shadow.dart';
 import 'package:photos/utils/navigation_util.dart';
 import 'package:photos/utils/navigation_util.dart';
 
 
 class LoadingPhotosWidget extends StatefulWidget {
 class LoadingPhotosWidget extends StatefulWidget {
-  const LoadingPhotosWidget({Key key}) : super(key: key);
+  const LoadingPhotosWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<LoadingPhotosWidget> createState() => _LoadingPhotosWidgetState();
   State<LoadingPhotosWidget> createState() => _LoadingPhotosWidgetState();
 }
 }
 
 
 class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
 class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
-  StreamSubscription<SyncStatusUpdate> _firstImportEvent;
-  StreamSubscription<LocalImportProgressEvent> _imprortProgressEvent;
+  late StreamSubscription<SyncStatusUpdate> _firstImportEvent;
+  late StreamSubscription<LocalImportProgressEvent> _imprortProgressEvent;
   int _currentPage = 0;
   int _currentPage = 0;
   String _loadingMessage = "Loading your photos...";
   String _loadingMessage = "Loading your photos...";
   final PageController _pageController = PageController(
   final PageController _pageController = PageController(
@@ -145,7 +145,7 @@ class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
                       children: [
                       children: [
                         Text(
                         Text(
                           "Did you know?",
                           "Did you know?",
-                          style: Theme.of(context).textTheme.headline6.copyWith(
+                          style: Theme.of(context).textTheme.headline6!.copyWith(
                                 color: Theme.of(context).colorScheme.greenText,
                                 color: Theme.of(context).colorScheme.greenText,
                               ),
                               ),
                         ),
                         ),
@@ -192,7 +192,7 @@ class _LoadingPhotosWidgetState extends State<LoadingPhotosWidget> {
       textAlign: TextAlign.start,
       textAlign: TextAlign.start,
       style: Theme.of(context)
       style: Theme.of(context)
           .textTheme
           .textTheme
-          .headline5
+          .headline5!
           .copyWith(color: Theme.of(context).colorScheme.defaultTextColor),
           .copyWith(color: Theme.of(context).colorScheme.defaultTextColor),
     );
     );
   }
   }

+ 93 - 93
lib/ui/nav_bar.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 library google_nav_bar;
 library google_nav_bar;
 
 
@@ -7,7 +7,7 @@ import 'package:flutter/services.dart';
 
 
 class GNav extends StatefulWidget {
 class GNav extends StatefulWidget {
   const GNav({
   const GNav({
-    Key key,
+    Key? key,
     this.tabs,
     this.tabs,
     this.selectedIndex = 0,
     this.selectedIndex = 0,
     this.onTabChange,
     this.onTabChange,
@@ -34,29 +34,29 @@ class GNav extends StatefulWidget {
     this.mainAxisAlignment = MainAxisAlignment.spaceBetween,
     this.mainAxisAlignment = MainAxisAlignment.spaceBetween,
   }) : super(key: key);
   }) : super(key: key);
 
 
-  final List<GButton> tabs;
+  final List<GButton>? tabs;
   final int selectedIndex;
   final int selectedIndex;
-  final Function onTabChange;
-  final double gap;
-  final double tabBorderRadius;
-  final double iconSize;
-  final Color activeColor;
-  final Color backgroundColor;
-  final Color tabBackgroundColor;
-  final Color color;
-  final Color rippleColor;
-  final Color hoverColor;
-  final EdgeInsetsGeometry padding;
-  final EdgeInsetsGeometry tabMargin;
-  final TextStyle textStyle;
-  final Duration duration;
-  final Curve curve;
-  final bool debug;
-  final bool haptic;
-  final Border tabBorder;
-  final Border tabActiveBorder;
-  final List<BoxShadow> tabShadow;
-  final Gradient tabBackgroundGradient;
+  final Function? onTabChange;
+  final double? gap;
+  final double? tabBorderRadius;
+  final double? iconSize;
+  final Color? activeColor;
+  final Color? backgroundColor;
+  final Color? tabBackgroundColor;
+  final Color? color;
+  final Color? rippleColor;
+  final Color? hoverColor;
+  final EdgeInsetsGeometry? padding;
+  final EdgeInsetsGeometry? tabMargin;
+  final TextStyle? textStyle;
+  final Duration? duration;
+  final Curve? curve;
+  final bool? debug;
+  final bool? haptic;
+  final Border? tabBorder;
+  final Border? tabActiveBorder;
+  final List<BoxShadow>? tabShadow;
+  final Gradient? tabBackgroundGradient;
   final MainAxisAlignment mainAxisAlignment;
   final MainAxisAlignment mainAxisAlignment;
 
 
   @override
   @override
@@ -64,7 +64,7 @@ class GNav extends StatefulWidget {
 }
 }
 
 
 class _GNavState extends State<GNav> {
 class _GNavState extends State<GNav> {
-  int selectedIndex;
+  int? selectedIndex;
   bool clickable = true;
   bool clickable = true;
 
 
   @override
   @override
@@ -83,20 +83,20 @@ class _GNavState extends State<GNav> {
       color: widget.backgroundColor ?? Colors.transparent,
       color: widget.backgroundColor ?? Colors.transparent,
       child: Row(
       child: Row(
         mainAxisAlignment: widget.mainAxisAlignment,
         mainAxisAlignment: widget.mainAxisAlignment,
-        children: widget.tabs
+        children: widget.tabs!
             .map(
             .map(
               (t) => GButton(
               (t) => GButton(
                 key: t.key,
                 key: t.key,
                 border: t.border ?? widget.tabBorder,
                 border: t.border ?? widget.tabBorder,
                 activeBorder: t.activeBorder ?? widget.tabActiveBorder,
                 activeBorder: t.activeBorder ?? widget.tabActiveBorder,
-                borderRadius: t.borderRadius ?? widget.tabBorderRadius != null
+                borderRadius: t.borderRadius as bool? ?? widget.tabBorderRadius != null
                     ? BorderRadius.all(
                     ? BorderRadius.all(
-                        Radius.circular(widget.tabBorderRadius),
+                        Radius.circular(widget.tabBorderRadius!),
                       )
                       )
                     : const BorderRadius.all(Radius.circular(100.0)),
                     : const BorderRadius.all(Radius.circular(100.0)),
                 debug: widget.debug ?? false,
                 debug: widget.debug ?? false,
                 margin: t.margin ?? widget.tabMargin,
                 margin: t.margin ?? widget.tabMargin,
-                active: selectedIndex == widget.tabs.indexOf(t),
+                active: selectedIndex == widget.tabs!.indexOf(t),
                 gap: t.gap ?? widget.gap,
                 gap: t.gap ?? widget.gap,
                 iconActiveColor: t.iconActiveColor ?? widget.activeColor,
                 iconActiveColor: t.iconActiveColor ?? widget.activeColor,
                 iconColor: t.iconColor ?? widget.color,
                 iconColor: t.iconColor ?? widget.color,
@@ -118,7 +118,7 @@ class _GNavState extends State<GNav> {
                     Colors.transparent,
                     Colors.transparent,
                 duration: widget.duration ?? const Duration(milliseconds: 500),
                 duration: widget.duration ?? const Duration(milliseconds: 500),
                 onPressed: () {
                 onPressed: () {
-                  widget.onTabChange(widget.tabs.indexOf(t));
+                  widget.onTabChange!(widget.tabs!.indexOf(t));
                 },
                 },
               ),
               ),
             )
             )
@@ -129,35 +129,35 @@ class _GNavState extends State<GNav> {
 }
 }
 
 
 class GButton extends StatefulWidget {
 class GButton extends StatefulWidget {
-  final bool active;
-  final bool debug;
-  final bool haptic;
-  final double gap;
-  final Color iconColor;
-  final Color rippleColor;
-  final Color hoverColor;
-  final Color iconActiveColor;
-  final Color textColor;
-  final EdgeInsetsGeometry padding;
-  final EdgeInsetsGeometry margin;
-  final TextStyle textStyle;
-  final double iconSize;
-  final Function onPressed;
+  final bool? active;
+  final bool? debug;
+  final bool? haptic;
+  final double? gap;
+  final Color? iconColor;
+  final Color? rippleColor;
+  final Color? hoverColor;
+  final Color? iconActiveColor;
+  final Color? textColor;
+  final EdgeInsetsGeometry? padding;
+  final EdgeInsetsGeometry? margin;
+  final TextStyle? textStyle;
+  final double? iconSize;
+  final Function? onPressed;
   final String text;
   final String text;
-  final IconData icon;
-  final Color backgroundColor;
-  final Duration duration;
-  final Curve curve;
-  final Gradient backgroundGradient;
-  final Widget leading;
-  final BorderRadius borderRadius;
-  final Border border;
-  final Border activeBorder;
-  final List<BoxShadow> shadow;
-  final String semanticLabel;
+  final IconData? icon;
+  final Color? backgroundColor;
+  final Duration? duration;
+  final Curve? curve;
+  final Gradient? backgroundGradient;
+  final Widget? leading;
+  final BorderRadius? borderRadius;
+  final Border? border;
+  final Border? activeBorder;
+  final List<BoxShadow>? shadow;
+  final String? semanticLabel;
 
 
   const GButton({
   const GButton({
-    Key key,
+    Key? key,
     this.active,
     this.active,
     this.haptic,
     this.haptic,
     this.backgroundColor,
     this.backgroundColor,
@@ -205,8 +205,8 @@ class _GButtonState extends State<GButton> {
         iconSize: widget.iconSize,
         iconSize: widget.iconSize,
         active: widget.active,
         active: widget.active,
         onPressed: () {
         onPressed: () {
-          if (widget.haptic) HapticFeedback.selectionClick();
-          widget.onPressed();
+          if (widget.haptic!) HapticFeedback.selectionClick();
+          widget.onPressed!();
         },
         },
         padding: widget.padding,
         padding: widget.padding,
         margin: widget.margin,
         margin: widget.margin,
@@ -227,7 +227,7 @@ class _GButtonState extends State<GButton> {
 
 
 class Button extends StatefulWidget {
 class Button extends StatefulWidget {
   const Button({
   const Button({
-    Key key,
+    Key? key,
     this.icon,
     this.icon,
     this.iconSize,
     this.iconSize,
     this.leading,
     this.leading,
@@ -252,38 +252,38 @@ class Button extends StatefulWidget {
     this.shadow,
     this.shadow,
   }) : super(key: key);
   }) : super(key: key);
 
 
-  final IconData icon;
-  final double iconSize;
-  final Text text;
-  final Widget leading;
-  final Color iconActiveColor;
-  final Color iconColor;
-  final Color color;
-  final Color rippleColor;
-  final Color hoverColor;
-  final double gap;
-  final bool active;
-  final bool debug;
-  final VoidCallback onPressed;
-  final EdgeInsetsGeometry padding;
-  final EdgeInsetsGeometry margin;
-  final Duration duration;
-  final Curve curve;
-  final Gradient gradient;
-  final BorderRadius borderRadius;
-  final Border border;
-  final Border activeBorder;
-  final List<BoxShadow> shadow;
+  final IconData? icon;
+  final double? iconSize;
+  final Text? text;
+  final Widget? leading;
+  final Color? iconActiveColor;
+  final Color? iconColor;
+  final Color? color;
+  final Color? rippleColor;
+  final Color? hoverColor;
+  final double? gap;
+  final bool? active;
+  final bool? debug;
+  final VoidCallback? onPressed;
+  final EdgeInsetsGeometry? padding;
+  final EdgeInsetsGeometry? margin;
+  final Duration? duration;
+  final Curve? curve;
+  final Gradient? gradient;
+  final BorderRadius? borderRadius;
+  final Border? border;
+  final Border? activeBorder;
+  final List<BoxShadow>? shadow;
 
 
   @override
   @override
   State<Button> createState() => _ButtonState();
   State<Button> createState() => _ButtonState();
 }
 }
 
 
 class _ButtonState extends State<Button> with TickerProviderStateMixin {
 class _ButtonState extends State<Button> with TickerProviderStateMixin {
-  bool _expanded;
+  bool? _expanded;
 
 
-  AnimationController expandController;
-  Animation<double> animation;
+  late AnimationController expandController;
+  Animation<double>? animation;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -302,8 +302,8 @@ class _ButtonState extends State<Button> with TickerProviderStateMixin {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    _expanded = !widget.active;
-    if (_expanded) {
+    _expanded = !widget.active!;
+    if (_expanded!) {
       expandController.reverse();
       expandController.reverse();
     } else {
     } else {
       expandController.forward();
       expandController.forward();
@@ -312,7 +312,7 @@ class _ButtonState extends State<Button> with TickerProviderStateMixin {
     final Widget icon = widget.leading ??
     final Widget icon = widget.leading ??
         Icon(
         Icon(
           widget.icon,
           widget.icon,
-          color: _expanded ? widget.iconColor : widget.iconActiveColor,
+          color: _expanded! ? widget.iconColor : widget.iconActiveColor,
           size: widget.iconSize,
           size: widget.iconSize,
         );
         );
 
 
@@ -323,23 +323,23 @@ class _ButtonState extends State<Button> with TickerProviderStateMixin {
         splashColor: widget.rippleColor,
         splashColor: widget.rippleColor,
         borderRadius: BorderRadius.circular(100),
         borderRadius: BorderRadius.circular(100),
         onTap: () {
         onTap: () {
-          widget.onPressed();
+          widget.onPressed!();
         },
         },
         child: Container(
         child: Container(
           padding: widget.margin,
           padding: widget.margin,
           child: AnimatedContainer(
           child: AnimatedContainer(
             curve: Curves.easeOut,
             curve: Curves.easeOut,
             padding: widget.padding,
             padding: widget.padding,
-            duration: widget.duration,
+            duration: widget.duration!,
             decoration: BoxDecoration(
             decoration: BoxDecoration(
               boxShadow: widget.shadow,
               boxShadow: widget.shadow,
-              border: widget.active
+              border: widget.active!
                   ? (widget.activeBorder ?? widget.border)
                   ? (widget.activeBorder ?? widget.border)
                   : widget.border,
                   : widget.border,
               gradient: widget.gradient,
               gradient: widget.gradient,
-              color: _expanded
-                  ? widget.color.withOpacity(0)
-                  : widget.debug
+              color: _expanded!
+                  ? widget.color!.withOpacity(0)
+                  : widget.debug!
                       ? Colors.red
                       ? Colors.red
                       : widget.gradient != null
                       : widget.gradient != null
                           ? Colors.white
                           ? Colors.white

+ 2 - 2
lib/ui/notification/update/change_log_page.dart

@@ -1,4 +1,3 @@
-import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/services/update_service.dart';
 import 'package:photos/services/update_service.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
@@ -87,7 +86,8 @@ class _ChangeLogPageState extends State<ChangeLogPage> {
                       iconColor: enteColorScheme.primary500,
                       iconColor: enteColorScheme.primary500,
                       onTap: () async {
                       onTap: () async {
                         launchUrlString(
                         launchUrlString(
-                            UpdateService.instance.getRateDetails().item2);
+                          UpdateService.instance.getRateDetails().item2,
+                        );
                       },
                       },
                     ),
                     ),
                     const SizedBox(height: 8),
                     const SizedBox(height: 8),

+ 12 - 16
lib/ui/payment/billing_questions_widget.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:convert';
 import 'dart:convert';
 
 
 import 'package:expansion_tile_card/expansion_tile_card.dart';
 import 'package:expansion_tile_card/expansion_tile_card.dart';
@@ -10,7 +8,7 @@ import 'package:photos/ui/common/loading_widget.dart';
 
 
 class BillingQuestionsWidget extends StatelessWidget {
 class BillingQuestionsWidget extends StatelessWidget {
   const BillingQuestionsWidget({
   const BillingQuestionsWidget({
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -64,11 +62,11 @@ class BillingQuestionsWidget extends StatelessWidget {
 
 
 class FaqWidget extends StatelessWidget {
 class FaqWidget extends StatelessWidget {
   const FaqWidget({
   const FaqWidget({
-    Key key,
-    @required this.faq,
+    Key? key,
+    required this.faq,
   }) : super(key: key);
   }) : super(key: key);
 
 
-  final FaqItem faq;
+  final FaqItem? faq;
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -76,7 +74,7 @@ class FaqWidget extends StatelessWidget {
       padding: const EdgeInsets.all(2),
       padding: const EdgeInsets.all(2),
       child: ExpansionTileCard(
       child: ExpansionTileCard(
         elevation: 0,
         elevation: 0,
-        title: Text(faq.q),
+        title: Text(faq!.q!),
         expandedTextColor: Theme.of(context).colorScheme.greenAlternative,
         expandedTextColor: Theme.of(context).colorScheme.greenAlternative,
         baseColor: Theme.of(context).cardColor,
         baseColor: Theme.of(context).cardColor,
         children: [
         children: [
@@ -87,7 +85,7 @@ class FaqWidget extends StatelessWidget {
               bottom: 12,
               bottom: 12,
             ),
             ),
             child: Text(
             child: Text(
-              faq.a,
+              faq!.a!,
               style: const TextStyle(
               style: const TextStyle(
                 height: 1.5,
                 height: 1.5,
               ),
               ),
@@ -100,16 +98,16 @@ class FaqWidget extends StatelessWidget {
 }
 }
 
 
 class FaqItem {
 class FaqItem {
-  final String q;
-  final String a;
+  final String? q;
+  final String? a;
   FaqItem({
   FaqItem({
     this.q,
     this.q,
     this.a,
     this.a,
   });
   });
 
 
   FaqItem copyWith({
   FaqItem copyWith({
-    String q,
-    String a,
+    String? q,
+    String? a,
   }) {
   }) {
     return FaqItem(
     return FaqItem(
       q: q ?? this.q,
       q: q ?? this.q,
@@ -125,11 +123,9 @@ class FaqItem {
   }
   }
 
 
   factory FaqItem.fromMap(Map<String, dynamic> map) {
   factory FaqItem.fromMap(Map<String, dynamic> map) {
-    if (map == null) return null;
-
     return FaqItem(
     return FaqItem(
-      q: map['q'],
-      a: map['a'],
+      q: map['q'] ?? 'q',
+      a: map['a'] ?? 'a',
     );
     );
   }
   }
 
 

+ 27 - 28
lib/ui/payment/payment_web_page.dart

@@ -1,7 +1,6 @@
-// @dart=2.9
-
 import 'dart:io';
 import 'dart:io';
 
 
+import 'package:collection/collection.dart' show IterableNullableExtension;
 import 'package:flutter/foundation.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
 import 'package:flutter_inappwebview/flutter_inappwebview.dart';
@@ -15,10 +14,10 @@ import 'package:photos/ui/common/progress_dialog.dart';
 import 'package:photos/utils/dialog_util.dart';
 import 'package:photos/utils/dialog_util.dart';
 
 
 class PaymentWebPage extends StatefulWidget {
 class PaymentWebPage extends StatefulWidget {
-  final String planId;
-  final String actionType;
+  final String? planId;
+  final String? actionType;
 
 
-  const PaymentWebPage({Key key, this.planId, this.actionType})
+  const PaymentWebPage({Key? key, this.planId, this.actionType})
       : super(key: key);
       : super(key: key);
 
 
   @override
   @override
@@ -30,10 +29,10 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
   final UserService userService = UserService.instance;
   final UserService userService = UserService.instance;
   final BillingService billingService = BillingService.instance;
   final BillingService billingService = BillingService.instance;
   final String basePaymentUrl = kWebPaymentBaseEndpoint;
   final String basePaymentUrl = kWebPaymentBaseEndpoint;
-  ProgressDialog _dialog;
-  InAppWebViewController webView;
+  late ProgressDialog _dialog;
+  InAppWebViewController? webView;
   double progress = 0;
   double progress = 0;
-  Uri initPaymentUrl;
+  Uri? initPaymentUrl;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -54,7 +53,7 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
       return const EnteLoadingWidget();
       return const EnteLoadingWidget();
     }
     }
     return WillPopScope(
     return WillPopScope(
-      onWillPop: () async => _buildPageExitWidget(context),
+      onWillPop: (() async => _buildPageExitWidget(context)),
       child: Scaffold(
       child: Scaffold(
         appBar: AppBar(
         appBar: AppBar(
           title: const Text('Subscription'),
           title: const Text('Subscription'),
@@ -83,7 +82,7 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
                   _logger.info("Loading url $loadingUri");
                   _logger.info("Loading url $loadingUri");
                   // handle the payment response
                   // handle the payment response
                   if (_isPaymentActionComplete(loadingUri)) {
                   if (_isPaymentActionComplete(loadingUri)) {
-                    await _handlePaymentResponse(loadingUri);
+                    await _handlePaymentResponse(loadingUri!);
                     return NavigationActionPolicy.CANCEL;
                     return NavigationActionPolicy.CANCEL;
                   }
                   }
                   return NavigationActionPolicy.ALLOW;
                   return NavigationActionPolicy.ALLOW;
@@ -113,7 +112,7 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
                 },
                 },
               ),
               ),
             ),
             ),
-          ].where((Object o) => o != null).toList(),
+          ].whereNotNull().toList(),
         ),
         ),
       ),
       ),
     );
     );
@@ -125,7 +124,7 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
     super.dispose();
     super.dispose();
   }
   }
 
 
-  Uri _getPaymentUrl(String paymentToken) {
+  Uri _getPaymentUrl(String? paymentToken) {
     final queryParameters = {
     final queryParameters = {
       'productID': widget.planId,
       'productID': widget.planId,
       'paymentToken': paymentToken,
       'paymentToken': paymentToken,
@@ -134,15 +133,15 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
     };
     };
     final tryParse = Uri.tryParse(kWebPaymentBaseEndpoint);
     final tryParse = Uri.tryParse(kWebPaymentBaseEndpoint);
     if (kDebugMode && kWebPaymentBaseEndpoint.startsWith("http://")) {
     if (kDebugMode && kWebPaymentBaseEndpoint.startsWith("http://")) {
-      return Uri.http(tryParse.authority, tryParse.path, queryParameters);
+      return Uri.http(tryParse!.authority, tryParse.path, queryParameters);
     } else {
     } else {
-      return Uri.https(tryParse.authority, tryParse.path, queryParameters);
+      return Uri.https(tryParse!.authority, tryParse.path, queryParameters);
     }
     }
   }
   }
 
 
   // show dialog to handle accidental back press.
   // show dialog to handle accidental back press.
-  Future<bool> _buildPageExitWidget(BuildContext context) {
-    return showDialog(
+  Future<bool> _buildPageExitWidget(BuildContext context) async {
+    final result = await showDialog(
       context: context,
       context: context,
       builder: (context) => AlertDialog(
       builder: (context) => AlertDialog(
         title: const Text('Are you sure you want to exit?'),
         title: const Text('Are you sure you want to exit?'),
@@ -168,9 +167,13 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
         ],
         ],
       ),
       ),
     );
     );
+    if (result != null) {
+      return result;
+    }
+    return false;
   }
   }
 
 
-  bool _isPaymentActionComplete(Uri loadingUri) {
+  bool _isPaymentActionComplete(Uri? loadingUri) {
     return loadingUri.toString().startsWith(kWebPaymentRedirectUrl);
     return loadingUri.toString().startsWith(kWebPaymentRedirectUrl);
   }
   }
 
 
@@ -221,14 +224,10 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
         paymentProvider: stripe,
         paymentProvider: stripe,
       );
       );
       await _dialog.hide();
       await _dialog.hide();
-      if (response != null) {
-        final content = widget.actionType == 'buy'
-            ? 'Your purchase was successful'
-            : 'Your subscription was updated successfully';
-        await _showExitPageDialog(title: 'Thank you', content: content);
-      } else {
-        throw Exception("verifySubscription api failed");
-      }
+      final content = widget.actionType == 'buy'
+          ? 'Your purchase was successful'
+          : 'Your subscription was updated successfully';
+      await _showExitPageDialog(title: 'Thank you', content: content);
     } catch (error) {
     } catch (error) {
       _logger.severe(error);
       _logger.severe(error);
       await _dialog.hide();
       await _dialog.hide();
@@ -240,13 +239,13 @@ class _PaymentWebPageState extends State<PaymentWebPage> {
   }
   }
 
 
   // warn the user to wait for sometime before trying another payment
   // warn the user to wait for sometime before trying another payment
-  Future<dynamic> _showExitPageDialog({String title, String content}) {
+  Future<dynamic> _showExitPageDialog({String? title, String? content}) {
     return showDialog(
     return showDialog(
       context: context,
       context: context,
       barrierDismissible: false,
       barrierDismissible: false,
       builder: (context) => AlertDialog(
       builder: (context) => AlertDialog(
-        title: Text(title),
-        content: Text(content),
+        title: Text(title!),
+        content: Text(content!),
         actions: <Widget>[
         actions: <Widget>[
           TextButton(
           TextButton(
             child: Text(
             child: Text(

+ 30 - 32
lib/ui/payment/stripe_subscription_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -30,7 +28,7 @@ class StripeSubscriptionPage extends StatefulWidget {
 
 
   const StripeSubscriptionPage({
   const StripeSubscriptionPage({
     this.isOnboarding = false,
     this.isOnboarding = false,
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -40,13 +38,13 @@ class StripeSubscriptionPage extends StatefulWidget {
 class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
 class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
   final _billingService = BillingService.instance;
   final _billingService = BillingService.instance;
   final _userService = UserService.instance;
   final _userService = UserService.instance;
-  Subscription _currentSubscription;
-  ProgressDialog _dialog;
-  UserDetails _userDetails;
+  Subscription? _currentSubscription;
+  late ProgressDialog _dialog;
+  late UserDetails _userDetails;
 
 
   // indicates if user's subscription plan is still active
   // indicates if user's subscription plan is still active
-  bool _hasActiveSubscription;
-  FreePlan _freePlan;
+  late bool _hasActiveSubscription;
+  late FreePlan _freePlan;
   List<BillingPlan> _plans = [];
   List<BillingPlan> _plans = [];
   bool _hasLoadedData = false;
   bool _hasLoadedData = false;
   bool _isLoading = false;
   bool _isLoading = false;
@@ -64,9 +62,9 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
         .then((userDetails) async {
         .then((userDetails) async {
       _userDetails = userDetails;
       _userDetails = userDetails;
       _currentSubscription = userDetails.subscription;
       _currentSubscription = userDetails.subscription;
-      _showYearlyPlan = _currentSubscription.isYearlyPlan();
-      _hasActiveSubscription = _currentSubscription.isValid();
-      _isStripeSubscriber = _currentSubscription.paymentProvider == stripe;
+      _showYearlyPlan = _currentSubscription!.isYearlyPlan();
+      _hasActiveSubscription = _currentSubscription!.isValid();
+      _isStripeSubscriber = _currentSubscription!.paymentProvider == stripe;
       return _filterStripeForUI().then((value) {
       return _filterStripeForUI().then((value) {
         _hasLoadedData = true;
         _hasLoadedData = true;
         setState(() {});
         setState(() {});
@@ -79,7 +77,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
     final billingPlans = await _billingService.getBillingPlans();
     final billingPlans = await _billingService.getBillingPlans();
     _freePlan = billingPlans.freePlan;
     _freePlan = billingPlans.freePlan;
     _plans = billingPlans.plans.where((plan) {
     _plans = billingPlans.plans.where((plan) {
-      if (plan.stripeID == null || plan.stripeID.isEmpty) {
+      if (plan.stripeID.isEmpty) {
         return false;
         return false;
       }
       }
       final isYearlyPlan = plan.period == 'year';
       final isYearlyPlan = plan.period == 'year';
@@ -101,8 +99,8 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
     // verify user has subscribed before redirecting to main page
     // verify user has subscribed before redirecting to main page
     if (widget.isOnboarding &&
     if (widget.isOnboarding &&
         _currentSubscription != null &&
         _currentSubscription != null &&
-        _currentSubscription.isValid() &&
-        _currentSubscription.productID != freeProductID) {
+        _currentSubscription!.isValid() &&
+        _currentSubscription!.productID != freeProductID) {
       Navigator.of(context).popUntil((route) => route.isFirst);
       Navigator.of(context).popUntil((route) => route.isFirst);
     }
     }
   }
   }
@@ -203,7 +201,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
       widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
       widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
     }
     }
 
 
-    if (_currentSubscription.productID == freeProductID) {
+    if (_currentSubscription!.productID == freeProductID) {
       if (widget.isOnboarding) {
       if (widget.isOnboarding) {
         widgets.add(SkipSubscriptionWidget(freePlan: _freePlan));
         widgets.add(SkipSubscriptionWidget(freePlan: _freePlan));
       }
       }
@@ -215,22 +213,22 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
       widgets.add(_stripeRenewOrCancelButton());
       widgets.add(_stripeRenewOrCancelButton());
     }
     }
 
 
-    if (_currentSubscription.productID != freeProductID) {
+    if (_currentSubscription!.productID != freeProductID) {
       widgets.addAll([
       widgets.addAll([
         Align(
         Align(
           alignment: Alignment.center,
           alignment: Alignment.center,
           child: GestureDetector(
           child: GestureDetector(
             onTap: () async {
             onTap: () async {
               final String paymentProvider =
               final String paymentProvider =
-                  _currentSubscription.paymentProvider;
-              switch (_currentSubscription.paymentProvider) {
+                  _currentSubscription!.paymentProvider;
+              switch (_currentSubscription!.paymentProvider) {
                 case stripe:
                 case stripe:
                   await _launchStripePortal();
                   await _launchStripePortal();
                   break;
                   break;
                 case playStore:
                 case playStore:
                   launchUrlString(
                   launchUrlString(
                     "https://play.google.com/store/account/subscriptions?sku=" +
                     "https://play.google.com/store/account/subscriptions?sku=" +
-                        _currentSubscription.productID +
+                        _currentSubscription!.productID +
                         "&package=io.ente.photos",
                         "&package=io.ente.photos",
                   );
                   );
                   break;
                   break;
@@ -288,7 +286,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
                   RichText(
                   RichText(
                     text: TextSpan(
                     text: TextSpan(
                       text: "Manage family",
                       text: "Manage family",
-                      style: Theme.of(context).textTheme.bodyMedium.copyWith(
+                      style: Theme.of(context).textTheme.bodyMedium!.copyWith(
                             decoration: TextDecoration.underline,
                             decoration: TextDecoration.underline,
                           ),
                           ),
                     ),
                     ),
@@ -330,7 +328,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
 
 
   Widget _stripeRenewOrCancelButton() {
   Widget _stripeRenewOrCancelButton() {
     final bool isRenewCancelled =
     final bool isRenewCancelled =
-        _currentSubscription.attributes?.isCancelled ?? false;
+        _currentSubscription!.attributes?.isCancelled ?? false;
     final String title =
     final String title =
         isRenewCancelled ? "Renew subscription" : "Cancel subscription";
         isRenewCancelled ? "Renew subscription" : "Cancel subscription";
     return TextButton(
     return TextButton(
@@ -393,11 +391,11 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
     bool foundActivePlan = false;
     bool foundActivePlan = false;
     for (final plan in _plans) {
     for (final plan in _plans) {
       final productID = plan.stripeID;
       final productID = plan.stripeID;
-      if (productID == null || productID.isEmpty) {
+      if (productID.isEmpty) {
         continue;
         continue;
       }
       }
-      final isActive =
-          _hasActiveSubscription && _currentSubscription.productID == productID;
+      final isActive = _hasActiveSubscription &&
+          _currentSubscription!.productID == productID;
       if (isActive) {
       if (isActive) {
         foundActivePlan = true;
         foundActivePlan = true;
       }
       }
@@ -412,12 +410,12 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
               // payment providers
               // payment providers
               if (!_isStripeSubscriber &&
               if (!_isStripeSubscriber &&
                   _hasActiveSubscription &&
                   _hasActiveSubscription &&
-                  _currentSubscription.productID != freeProductID) {
+                  _currentSubscription!.productID != freeProductID) {
                 showErrorDialog(
                 showErrorDialog(
                   context,
                   context,
                   "Sorry",
                   "Sorry",
                   "Please cancel your existing subscription from "
                   "Please cancel your existing subscription from "
-                      "${_currentSubscription.paymentProvider} first",
+                      "${_currentSubscription!.paymentProvider} first",
                 );
                 );
                 return;
                 return;
               }
               }
@@ -515,13 +513,13 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
   void _addCurrentPlanWidget(List<Widget> planWidgets) {
   void _addCurrentPlanWidget(List<Widget> planWidgets) {
     // don't add current plan if it's monthly plan but UI is showing yearly plans
     // don't add current plan if it's monthly plan but UI is showing yearly plans
     // and vice versa.
     // and vice versa.
-    if (_showYearlyPlan != _currentSubscription.isYearlyPlan() &&
-        _currentSubscription.productID != freeProductID) {
+    if (_showYearlyPlan != _currentSubscription!.isYearlyPlan() &&
+        _currentSubscription!.productID != freeProductID) {
       return;
       return;
     }
     }
     int activePlanIndex = 0;
     int activePlanIndex = 0;
     for (; activePlanIndex < _plans.length; activePlanIndex++) {
     for (; activePlanIndex < _plans.length; activePlanIndex++) {
-      if (_plans[activePlanIndex].storage > _currentSubscription.storage) {
+      if (_plans[activePlanIndex].storage > _currentSubscription!.storage) {
         break;
         break;
       }
       }
     }
     }
@@ -531,9 +529,9 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
         child: InkWell(
         child: InkWell(
           onTap: () {},
           onTap: () {},
           child: SubscriptionPlanWidget(
           child: SubscriptionPlanWidget(
-            storage: _currentSubscription.storage,
-            price: _currentSubscription.price,
-            period: _currentSubscription.period,
+            storage: _currentSubscription!.storage,
+            price: _currentSubscription!.price,
+            period: _currentSubscription!.period,
             isActive: true,
             isActive: true,
           ),
           ),
         ),
         ),

+ 14 - 14
lib/ui/payment/subscription_common_widgets.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/ente_theme_data.dart';
 import 'package:photos/ente_theme_data.dart';
@@ -8,11 +8,11 @@ import 'package:photos/utils/data_util.dart';
 import 'package:photos/utils/date_time_util.dart';
 import 'package:photos/utils/date_time_util.dart';
 
 
 class SubscriptionHeaderWidget extends StatefulWidget {
 class SubscriptionHeaderWidget extends StatefulWidget {
-  final bool isOnboarding;
-  final int currentUsage;
+  final bool? isOnboarding;
+  final int? currentUsage;
 
 
   const SubscriptionHeaderWidget({
   const SubscriptionHeaderWidget({
-    Key key,
+    Key? key,
     this.isOnboarding,
     this.isOnboarding,
     this.currentUsage,
     this.currentUsage,
   }) : super(key: key);
   }) : super(key: key);
@@ -26,7 +26,7 @@ class SubscriptionHeaderWidget extends StatefulWidget {
 class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
 class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    if (widget.isOnboarding) {
+    if (widget.isOnboarding!) {
       return Padding(
       return Padding(
         padding: const EdgeInsets.fromLTRB(20, 20, 20, 24),
         padding: const EdgeInsets.fromLTRB(20, 20, 20, 24),
         child: Column(
         child: Column(
@@ -64,10 +64,10 @@ class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
                   style: Theme.of(context).textTheme.subtitle1,
                   style: Theme.of(context).textTheme.subtitle1,
                 ),
                 ),
                 TextSpan(
                 TextSpan(
-                  text: formatBytes(widget.currentUsage),
+                  text: formatBytes(widget.currentUsage!),
                   style: Theme.of(context)
                   style: Theme.of(context)
                       .textTheme
                       .textTheme
-                      .subtitle1
+                      .subtitle1!
                       .copyWith(fontWeight: FontWeight.bold),
                       .copyWith(fontWeight: FontWeight.bold),
                 )
                 )
               ],
               ],
@@ -80,9 +80,9 @@ class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
 }
 }
 
 
 class ValidityWidget extends StatelessWidget {
 class ValidityWidget extends StatelessWidget {
-  final Subscription currentSubscription;
+  final Subscription? currentSubscription;
 
 
-  const ValidityWidget({Key key, this.currentSubscription}) : super(key: key);
+  const ValidityWidget({Key? key, this.currentSubscription}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -90,12 +90,12 @@ class ValidityWidget extends StatelessWidget {
       return const SizedBox.shrink();
       return const SizedBox.shrink();
     }
     }
     final endDate = getDateAndMonthAndYear(
     final endDate = getDateAndMonthAndYear(
-      DateTime.fromMicrosecondsSinceEpoch(currentSubscription.expiryTime),
+      DateTime.fromMicrosecondsSinceEpoch(currentSubscription!.expiryTime),
     );
     );
     var message = "Renews on $endDate";
     var message = "Renews on $endDate";
-    if (currentSubscription.productID == freeProductID) {
+    if (currentSubscription!.productID == freeProductID) {
       message = "Free trial valid till $endDate";
       message = "Free trial valid till $endDate";
-    } else if (currentSubscription.attributes?.isCancelled ?? false) {
+    } else if (currentSubscription!.attributes?.isCancelled ?? false) {
       message = "Your subscription will be cancelled on $endDate";
       message = "Your subscription will be cancelled on $endDate";
     }
     }
     return Padding(
     return Padding(
@@ -109,7 +109,7 @@ class ValidityWidget extends StatelessWidget {
 }
 }
 
 
 class SubFaqWidget extends StatelessWidget {
 class SubFaqWidget extends StatelessWidget {
-  const SubFaqWidget({Key key}) : super(key: key);
+  const SubFaqWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -132,7 +132,7 @@ class SubFaqWidget extends StatelessWidget {
           child: RichText(
           child: RichText(
             text: TextSpan(
             text: TextSpan(
               text: "Questions?",
               text: "Questions?",
-              style: Theme.of(context).textTheme.bodyMedium.copyWith(
+              style: Theme.of(context).textTheme.bodyMedium!.copyWith(
                     decoration: TextDecoration.underline,
                     decoration: TextDecoration.underline,
                   ),
                   ),
             ),
             ),

+ 33 - 35
lib/ui/payment/subscription_page.dart

@@ -1,5 +1,3 @@
-// @dart=2.9
-
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
 
 
@@ -29,7 +27,7 @@ class SubscriptionPage extends StatefulWidget {
 
 
   const SubscriptionPage({
   const SubscriptionPage({
     this.isOnboarding = false,
     this.isOnboarding = false,
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -40,16 +38,16 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
   final _logger = Logger("SubscriptionPage");
   final _logger = Logger("SubscriptionPage");
   final _billingService = BillingService.instance;
   final _billingService = BillingService.instance;
   final _userService = UserService.instance;
   final _userService = UserService.instance;
-  Subscription _currentSubscription;
-  StreamSubscription _purchaseUpdateSubscription;
-  ProgressDialog _dialog;
-  UserDetails _userDetails;
-  bool _hasActiveSubscription;
-  FreePlan _freePlan;
-  List<BillingPlan> _plans;
+  Subscription? _currentSubscription;
+  late StreamSubscription _purchaseUpdateSubscription;
+  late ProgressDialog _dialog;
+  late UserDetails _userDetails;
+  late bool _hasActiveSubscription;
+  late FreePlan _freePlan;
+  late List<BillingPlan> _plans;
   bool _hasLoadedData = false;
   bool _hasLoadedData = false;
   bool _isLoading = false;
   bool _isLoading = false;
-  bool _isActiveStripeSubscriber;
+  late bool _isActiveStripeSubscriber;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -76,9 +74,9 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
             String text = "Thank you for subscribing!";
             String text = "Thank you for subscribing!";
             if (!widget.isOnboarding) {
             if (!widget.isOnboarding) {
               final isUpgrade = _hasActiveSubscription &&
               final isUpgrade = _hasActiveSubscription &&
-                  newSubscription.storage > _currentSubscription.storage;
+                  newSubscription.storage > _currentSubscription!.storage;
               final isDowngrade = _hasActiveSubscription &&
               final isDowngrade = _hasActiveSubscription &&
-                  newSubscription.storage < _currentSubscription.storage;
+                  newSubscription.storage < _currentSubscription!.storage;
               if (isUpgrade) {
               if (isUpgrade) {
                 text = "Your plan was successfully upgraded";
                 text = "Your plan was successfully upgraded";
               } else if (isDowngrade) {
               } else if (isDowngrade) {
@@ -87,7 +85,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
             }
             }
             showShortToast(context, text);
             showShortToast(context, text);
             _currentSubscription = newSubscription;
             _currentSubscription = newSubscription;
-            _hasActiveSubscription = _currentSubscription.isValid();
+            _hasActiveSubscription = _currentSubscription!.isValid();
             setState(() {});
             setState(() {});
             await _dialog.hide();
             await _dialog.hide();
             Bus.instance.fire(SubscriptionPurchasedEvent());
             Bus.instance.fire(SubscriptionPurchasedEvent());
@@ -155,18 +153,18 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
     _userService.getUserDetailsV2(memoryCount: false).then((userDetails) async {
     _userService.getUserDetailsV2(memoryCount: false).then((userDetails) async {
       _userDetails = userDetails;
       _userDetails = userDetails;
       _currentSubscription = userDetails.subscription;
       _currentSubscription = userDetails.subscription;
-      _hasActiveSubscription = _currentSubscription.isValid();
+      _hasActiveSubscription = _currentSubscription!.isValid();
       final billingPlans = await _billingService.getBillingPlans();
       final billingPlans = await _billingService.getBillingPlans();
       _isActiveStripeSubscriber =
       _isActiveStripeSubscriber =
-          _currentSubscription.paymentProvider == stripe &&
-              _currentSubscription.isValid();
+          _currentSubscription!.paymentProvider == stripe &&
+              _currentSubscription!.isValid();
       _plans = billingPlans.plans.where((plan) {
       _plans = billingPlans.plans.where((plan) {
         final productID = _isActiveStripeSubscriber
         final productID = _isActiveStripeSubscriber
             ? plan.stripeID
             ? plan.stripeID
             : Platform.isAndroid
             : Platform.isAndroid
                 ? plan.androidID
                 ? plan.androidID
                 : plan.iosID;
                 : plan.iosID;
-        return productID != null && productID.isNotEmpty;
+        return productID.isNotEmpty;
       }).toList();
       }).toList();
       _freePlan = billingPlans.freePlan;
       _freePlan = billingPlans.freePlan;
       _hasLoadedData = true;
       _hasLoadedData = true;
@@ -210,7 +208,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
       widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
       widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
     }
     }
 
 
-    if (_currentSubscription.productID == freeProductID) {
+    if (_currentSubscription!.productID == freeProductID) {
       if (widget.isOnboarding) {
       if (widget.isOnboarding) {
         widgets.add(SkipSubscriptionWidget(freePlan: _freePlan));
         widgets.add(SkipSubscriptionWidget(freePlan: _freePlan));
       }
       }
@@ -218,20 +216,20 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
     }
     }
 
 
     if (_hasActiveSubscription &&
     if (_hasActiveSubscription &&
-        _currentSubscription.productID != freeProductID) {
+        _currentSubscription!.productID != freeProductID) {
       widgets.addAll([
       widgets.addAll([
         Align(
         Align(
           alignment: Alignment.center,
           alignment: Alignment.center,
           child: GestureDetector(
           child: GestureDetector(
             onTap: () {
             onTap: () {
               final String paymentProvider =
               final String paymentProvider =
-                  _currentSubscription.paymentProvider;
+                  _currentSubscription!.paymentProvider;
               if (paymentProvider == appStore && !Platform.isAndroid) {
               if (paymentProvider == appStore && !Platform.isAndroid) {
                 launchUrlString("https://apps.apple.com/account/billing");
                 launchUrlString("https://apps.apple.com/account/billing");
               } else if (paymentProvider == playStore && Platform.isAndroid) {
               } else if (paymentProvider == playStore && Platform.isAndroid) {
                 launchUrlString(
                 launchUrlString(
                   "https://play.google.com/store/account/subscriptions?sku=" +
                   "https://play.google.com/store/account/subscriptions?sku=" +
-                      _currentSubscription.productID +
+                      _currentSubscription!.productID +
                       "&package=io.ente.photos",
                       "&package=io.ente.photos",
                 );
                 );
               } else if (paymentProvider == stripe) {
               } else if (paymentProvider == stripe) {
@@ -294,7 +292,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
                   RichText(
                   RichText(
                     text: TextSpan(
                     text: TextSpan(
                       text: "Manage family",
                       text: "Manage family",
-                      style: Theme.of(context).textTheme.bodyMedium.copyWith(
+                      style: Theme.of(context).textTheme.bodyMedium!.copyWith(
                             decoration: TextDecoration.underline,
                             decoration: TextDecoration.underline,
                           ),
                           ),
                     ),
                     ),
@@ -320,11 +318,11 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
     bool foundActivePlan = false;
     bool foundActivePlan = false;
     for (final plan in _plans) {
     for (final plan in _plans) {
       final productID = plan.stripeID;
       final productID = plan.stripeID;
-      if (productID == null || productID.isEmpty) {
+      if (productID.isEmpty) {
         continue;
         continue;
       }
       }
-      final isActive =
-          _hasActiveSubscription && _currentSubscription.productID == productID;
+      final isActive = _hasActiveSubscription &&
+          _currentSubscription!.productID == productID;
       if (isActive) {
       if (isActive) {
         foundActivePlan = true;
         foundActivePlan = true;
       }
       }
@@ -362,7 +360,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
     bool foundActivePlan = false;
     bool foundActivePlan = false;
     final List<Widget> planWidgets = [];
     final List<Widget> planWidgets = [];
     if (_hasActiveSubscription &&
     if (_hasActiveSubscription &&
-        _currentSubscription.productID == freeProductID) {
+        _currentSubscription!.productID == freeProductID) {
       foundActivePlan = true;
       foundActivePlan = true;
       planWidgets.add(
       planWidgets.add(
         SubscriptionPlanWidget(
         SubscriptionPlanWidget(
@@ -375,8 +373,8 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
     }
     }
     for (final plan in _plans) {
     for (final plan in _plans) {
       final productID = Platform.isAndroid ? plan.androidID : plan.iosID;
       final productID = Platform.isAndroid ? plan.androidID : plan.iosID;
-      final isActive =
-          _hasActiveSubscription && _currentSubscription.productID == productID;
+      final isActive = _hasActiveSubscription &&
+          _currentSubscription!.productID == productID;
       if (isActive) {
       if (isActive) {
         foundActivePlan = true;
         foundActivePlan = true;
       }
       }
@@ -408,8 +406,8 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
               }
               }
               final isCrossGradingOnAndroid = Platform.isAndroid &&
               final isCrossGradingOnAndroid = Platform.isAndroid &&
                   _hasActiveSubscription &&
                   _hasActiveSubscription &&
-                  _currentSubscription.productID != freeProductID &&
-                  _currentSubscription.productID != plan.androidID;
+                  _currentSubscription!.productID != freeProductID &&
+                  _currentSubscription!.productID != plan.androidID;
               if (isCrossGradingOnAndroid) {
               if (isCrossGradingOnAndroid) {
                 await _dialog.hide();
                 await _dialog.hide();
                 showErrorDialog(
                 showErrorDialog(
@@ -445,7 +443,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
   void _addCurrentPlanWidget(List<Widget> planWidgets) {
   void _addCurrentPlanWidget(List<Widget> planWidgets) {
     int activePlanIndex = 0;
     int activePlanIndex = 0;
     for (; activePlanIndex < _plans.length; activePlanIndex++) {
     for (; activePlanIndex < _plans.length; activePlanIndex++) {
-      if (_plans[activePlanIndex].storage > _currentSubscription.storage) {
+      if (_plans[activePlanIndex].storage > _currentSubscription!.storage) {
         break;
         break;
       }
       }
     }
     }
@@ -455,9 +453,9 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
         child: InkWell(
         child: InkWell(
           onTap: () {},
           onTap: () {},
           child: SubscriptionPlanWidget(
           child: SubscriptionPlanWidget(
-            storage: _currentSubscription.storage,
-            price: _currentSubscription.price,
-            period: _currentSubscription.period,
+            storage: _currentSubscription!.storage,
+            price: _currentSubscription!.price,
+            period: _currentSubscription!.period,
             isActive: true,
             isActive: true,
           ),
           ),
         ),
         ),

+ 7 - 7
lib/ui/payment/subscription_plan_widget.dart

@@ -1,14 +1,14 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:photos/utils/data_util.dart';
 import 'package:photos/utils/data_util.dart';
 
 
 class SubscriptionPlanWidget extends StatelessWidget {
 class SubscriptionPlanWidget extends StatelessWidget {
   const SubscriptionPlanWidget({
   const SubscriptionPlanWidget({
-    Key key,
-    @required this.storage,
-    @required this.price,
-    @required this.period,
+    Key? key,
+    required this.storage,
+    required this.price,
+    required this.period,
     this.isActive = false,
     this.isActive = false,
   }) : super(key: key);
   }) : super(key: key);
 
 
@@ -57,12 +57,12 @@ class SubscriptionPlanWidget extends StatelessWidget {
                   convertBytesToReadableFormat(storage),
                   convertBytesToReadableFormat(storage),
                   style: Theme.of(context)
                   style: Theme.of(context)
                       .textTheme
                       .textTheme
-                      .headline6
+                      .headline6!
                       .copyWith(color: textColor),
                       .copyWith(color: textColor),
                 ),
                 ),
                 Text(
                 Text(
                   _displayPrice(),
                   _displayPrice(),
-                  style: Theme.of(context).textTheme.headline6.copyWith(
+                  style: Theme.of(context).textTheme.headline6!.copyWith(
                         color: textColor,
                         color: textColor,
                         fontWeight: FontWeight.normal,
                         fontWeight: FontWeight.normal,
                       ),
                       ),

+ 3 - 1
lib/ui/settings/about_section_widget.dart

@@ -78,7 +78,9 @@ class AboutSectionWidget extends StatelessWidget {
                         );
                         );
                       } else {
                       } else {
                         showShortToast(
                         showShortToast(
-                            context, "You are on the latest version");
+                          context,
+                          "You are on the latest version",
+                        );
                       }
                       }
                     },
                     },
                   ),
                   ),

+ 15 - 15
lib/ui/settings/app_update_dialog.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:logging/logging.dart';
 import 'package:logging/logging.dart';
@@ -11,9 +11,9 @@ import 'package:photos/theme/ente_theme.dart';
 import 'package:url_launcher/url_launcher_string.dart';
 import 'package:url_launcher/url_launcher_string.dart';
 
 
 class AppUpdateDialog extends StatefulWidget {
 class AppUpdateDialog extends StatefulWidget {
-  final LatestVersionInfo latestVersionInfo;
+  final LatestVersionInfo? latestVersionInfo;
 
 
-  const AppUpdateDialog(this.latestVersionInfo, {Key key}) : super(key: key);
+  const AppUpdateDialog(this.latestVersionInfo, {Key? key}) : super(key: key);
 
 
   @override
   @override
   State<AppUpdateDialog> createState() => _AppUpdateDialogState();
   State<AppUpdateDialog> createState() => _AppUpdateDialogState();
@@ -25,7 +25,7 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
     final List<Widget> changelog = [];
     final List<Widget> changelog = [];
     final enteTextTheme = getEnteTextTheme(context);
     final enteTextTheme = getEnteTextTheme(context);
     final enteColor = getEnteColorScheme(context);
     final enteColor = getEnteColorScheme(context);
-    for (final log in widget.latestVersionInfo.changelog) {
+    for (final log in widget.latestVersionInfo!.changelog) {
       changelog.add(
       changelog.add(
         Padding(
         Padding(
           padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
           padding: const EdgeInsets.fromLTRB(0, 4, 0, 4),
@@ -68,7 +68,7 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
           width: double.infinity,
           width: double.infinity,
           height: 56,
           height: 56,
           child: OutlinedButton(
           child: OutlinedButton(
-            style: Theme.of(context).outlinedButtonTheme.style.copyWith(
+            style: Theme.of(context).outlinedButtonTheme.style!.copyWith(
               textStyle: MaterialStateProperty.resolveWith<TextStyle>(
               textStyle: MaterialStateProperty.resolveWith<TextStyle>(
                 (Set<MaterialState> states) {
                 (Set<MaterialState> states) {
                   return enteTextTheme.bodyBold;
                   return enteTextTheme.bodyBold;
@@ -97,11 +97,11 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
               "Install manually",
               "Install manually",
               style: Theme.of(context)
               style: Theme.of(context)
                   .textTheme
                   .textTheme
-                  .caption
+                  .caption!
                   .copyWith(decoration: TextDecoration.underline),
                   .copyWith(decoration: TextDecoration.underline),
             ),
             ),
             onTap: () => launchUrlString(
             onTap: () => launchUrlString(
-              widget.latestVersionInfo.url,
+              widget.latestVersionInfo!.url,
               mode: LaunchMode.externalApplication,
               mode: LaunchMode.externalApplication,
             ),
             ),
           ),
           ),
@@ -109,7 +109,7 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
       ],
       ],
     );
     );
     final shouldForceUpdate =
     final shouldForceUpdate =
-        UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo);
+        UpdateService.instance.shouldForceUpdate(widget.latestVersionInfo!);
     return WillPopScope(
     return WillPopScope(
       onWillPop: () async => !shouldForceUpdate,
       onWillPop: () async => !shouldForceUpdate,
       child: AlertDialog(
       child: AlertDialog(
@@ -139,24 +139,24 @@ class _AppUpdateDialogState extends State<AppUpdateDialog> {
 }
 }
 
 
 class ApkDownloaderDialog extends StatefulWidget {
 class ApkDownloaderDialog extends StatefulWidget {
-  final LatestVersionInfo versionInfo;
+  final LatestVersionInfo? versionInfo;
 
 
-  const ApkDownloaderDialog(this.versionInfo, {Key key}) : super(key: key);
+  const ApkDownloaderDialog(this.versionInfo, {Key? key}) : super(key: key);
 
 
   @override
   @override
   State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
   State<ApkDownloaderDialog> createState() => _ApkDownloaderDialogState();
 }
 }
 
 
 class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
 class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
-  String _saveUrl;
-  double _downloadProgress;
+  String? _saveUrl;
+  double? _downloadProgress;
 
 
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
     _saveUrl = Configuration.instance.getTempDirectory() +
     _saveUrl = Configuration.instance.getTempDirectory() +
         "ente-" +
         "ente-" +
-        widget.versionInfo.name +
+        widget.versionInfo!.name +
         ".apk";
         ".apk";
     _downloadApk();
     _downloadApk();
   }
   }
@@ -186,11 +186,11 @@ class _ApkDownloaderDialogState extends State<ApkDownloaderDialog> {
   Future<void> _downloadApk() async {
   Future<void> _downloadApk() async {
     try {
     try {
       await Network.instance.getDio().download(
       await Network.instance.getDio().download(
-        widget.versionInfo.url,
+        widget.versionInfo!.url,
         _saveUrl,
         _saveUrl,
         onReceiveProgress: (count, _) {
         onReceiveProgress: (count, _) {
           setState(() {
           setState(() {
-            _downloadProgress = count / widget.versionInfo.size;
+            _downloadProgress = count / widget.versionInfo!.size;
           });
           });
         },
         },
       );
       );

+ 4 - 4
lib/ui/settings/backup_section_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:io';
 import 'dart:io';
 
 
@@ -26,7 +26,7 @@ import 'package:photos/utils/toast_util.dart';
 import 'package:url_launcher/url_launcher_string.dart';
 import 'package:url_launcher/url_launcher_string.dart';
 
 
 class BackupSectionWidget extends StatefulWidget {
 class BackupSectionWidget extends StatefulWidget {
-  const BackupSectionWidget({Key key}) : super(key: key);
+  const BackupSectionWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   BackupSectionWidgetState createState() => BackupSectionWidgetState();
   BackupSectionWidgetState createState() => BackupSectionWidgetState();
@@ -108,7 +108,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
                 "You've no files on this device that can be deleted",
                 "You've no files on this device that can be deleted",
               );
               );
             } else {
             } else {
-              final bool result =
+              final bool? result =
                   await routeToPage(context, FreeSpacePage(status));
                   await routeToPage(context, FreeSpacePage(status));
               if (result == true) {
               if (result == true) {
                 _showSpaceFreedDialog(status);
                 _showSpaceFreedDialog(status);
@@ -145,7 +145,7 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
                 "You've no duplicate files that can be cleared",
                 "You've no duplicate files that can be cleared",
               );
               );
             } else {
             } else {
-              final DeduplicationResult result =
+              final DeduplicationResult? result =
                   await routeToPage(context, DeduplicatePage(duplicates));
                   await routeToPage(context, DeduplicatePage(duplicates));
               if (result != null) {
               if (result != null) {
                 _showDuplicateFilesDeletedDialog(result);
                 _showDuplicateFilesDeletedDialog(result);

+ 0 - 2
lib/ui/settings/settings_title_bar_widget.dart

@@ -1,6 +1,5 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:intl/intl.dart';
 import 'package:intl/intl.dart';
-import 'package:logging/logging.dart';
 import 'package:photos/models/user_details.dart';
 import 'package:photos/models/user_details.dart';
 import 'package:photos/states/user_details_state.dart';
 import 'package:photos/states/user_details_state.dart';
 import 'package:photos/theme/ente_theme.dart';
 import 'package:photos/theme/ente_theme.dart';
@@ -11,7 +10,6 @@ class SettingsTitleBarWidget extends StatelessWidget {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final logger = Logger((SettingsTitleBarWidget).toString());
     final inheritedDetails = InheritedUserDetails.of(context);
     final inheritedDetails = InheritedUserDetails.of(context);
     final userDetails = inheritedDetails?.userDetails;
     final userDetails = inheritedDetails?.userDetails;
     bool isCached = false;
     bool isCached = false;

+ 0 - 1
lib/ui/settings/social_section_widget.dart

@@ -32,7 +32,6 @@ class SocialSectionWidget extends StatelessWidget {
     );
     );
     options.addAll(
     options.addAll(
       [
       [
-        sectionOptionSpacing,
         const SocialsMenuItemWidget("Blog", "https://ente.io/blog"),
         const SocialsMenuItemWidget("Blog", "https://ente.io/blog"),
         sectionOptionSpacing,
         sectionOptionSpacing,
         const SocialsMenuItemWidget("Twitter", "https://twitter.com/enteio"),
         const SocialsMenuItemWidget("Twitter", "https://twitter.com/enteio"),

+ 4 - 4
lib/ui/settings/theme_switch_widget.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:adaptive_theme/adaptive_theme.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
@@ -11,14 +11,14 @@ import 'package:photos/ui/components/menu_item_widget.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 import 'package:photos/ui/settings/common_settings.dart';
 
 
 class ThemeSwitchWidget extends StatefulWidget {
 class ThemeSwitchWidget extends StatefulWidget {
-  const ThemeSwitchWidget({Key key}) : super(key: key);
+  const ThemeSwitchWidget({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<ThemeSwitchWidget> createState() => _ThemeSwitchWidgetState();
   State<ThemeSwitchWidget> createState() => _ThemeSwitchWidgetState();
 }
 }
 
 
 class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
 class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
-  AdaptiveThemeMode currentThemeMode;
+  AdaptiveThemeMode? currentThemeMode;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -67,7 +67,7 @@ class _ThemeSwitchWidgetState extends State<ThemeSwitchWidget> {
   Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) {
   Widget _menuItem(BuildContext context, AdaptiveThemeMode themeMode) {
     return MenuItemWidget(
     return MenuItemWidget(
       captionedTextWidget: CaptionedTextWidget(
       captionedTextWidget: CaptionedTextWidget(
-        title: toBeginningOfSentenceCase(themeMode.name),
+        title: toBeginningOfSentenceCase(themeMode.name)!,
         textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body,
         textStyle: Theme.of(context).colorScheme.enteTheme.textTheme.body,
       ),
       ),
       pressedColor: getEnteColorScheme(context).fillFaint,
       pressedColor: getEnteColorScheme(context).fillFaint,

+ 5 - 5
lib/ui/settings_page.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:io';
 import 'dart:io';
 
 
@@ -24,8 +24,8 @@ import 'package:photos/ui/settings/support_section_widget.dart';
 import 'package:photos/ui/settings/theme_switch_widget.dart';
 import 'package:photos/ui/settings/theme_switch_widget.dart';
 
 
 class SettingsPage extends StatelessWidget {
 class SettingsPage extends StatelessWidget {
-  final ValueNotifier<String> emailNotifier;
-  const SettingsPage({Key key, @required this.emailNotifier}) : super(key: key);
+  final ValueNotifier<String?> emailNotifier;
+  const SettingsPage({Key? key, required this.emailNotifier}) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
@@ -54,9 +54,9 @@ class SettingsPage extends StatelessWidget {
           child: AnimatedBuilder(
           child: AnimatedBuilder(
             // [AnimatedBuilder] accepts any [Listenable] subtype.
             // [AnimatedBuilder] accepts any [Listenable] subtype.
             animation: emailNotifier,
             animation: emailNotifier,
-            builder: (BuildContext context, Widget child) {
+            builder: (BuildContext context, Widget? child) {
               return Text(
               return Text(
-                emailNotifier.value,
+                emailNotifier.value!,
                 style: enteTextTheme.body.copyWith(
                 style: enteTextTheme.body.copyWith(
                   color: colorScheme.textMuted,
                   color: colorScheme.textMuted,
                   overflow: TextOverflow.ellipsis,
                   overflow: TextOverflow.ellipsis,

+ 28 - 28
lib/ui/shared_collections_gallery.dart

@@ -1,4 +1,4 @@
-// @dart=2.9
+
 
 
 import 'dart:async';
 import 'dart:async';
 import 'dart:math';
 import 'dart:math';
@@ -28,7 +28,7 @@ import 'package:photos/utils/share_util.dart';
 import 'package:photos/utils/toast_util.dart';
 import 'package:photos/utils/toast_util.dart';
 
 
 class SharedCollectionGallery extends StatefulWidget {
 class SharedCollectionGallery extends StatefulWidget {
-  const SharedCollectionGallery({Key key}) : super(key: key);
+  const SharedCollectionGallery({Key? key}) : super(key: key);
 
 
   @override
   @override
   State<SharedCollectionGallery> createState() =>
   State<SharedCollectionGallery> createState() =>
@@ -38,9 +38,9 @@ class SharedCollectionGallery extends StatefulWidget {
 class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
 class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
     with AutomaticKeepAliveClientMixin {
     with AutomaticKeepAliveClientMixin {
   final Logger _logger = Logger("SharedCollectionGallery");
   final Logger _logger = Logger("SharedCollectionGallery");
-  StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
-  StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
-  StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
+  late StreamSubscription<LocalPhotosUpdatedEvent> _localFilesSubscription;
+  late StreamSubscription<CollectionUpdatedEvent> _collectionUpdatesSubscription;
+  late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
 
 
   @override
   @override
   void initState() {
   void initState() {
@@ -71,10 +71,10 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
         final List<CollectionWithThumbnail> incoming = [];
         final List<CollectionWithThumbnail> incoming = [];
         for (final file in files) {
         for (final file in files) {
           final c =
           final c =
-              CollectionsService.instance.getCollectionByID(file.collectionID);
-          if (c.owner.id == Configuration.instance.getUserID()) {
-            if (c.sharees.isNotEmpty ||
-                c.publicURLs.isNotEmpty ||
+              CollectionsService.instance.getCollectionByID(file.collectionID!)!;
+          if (c.owner!.id == Configuration.instance.getUserID()) {
+            if (c.sharees!.isNotEmpty ||
+                c.publicURLs!.isNotEmpty ||
                 c.isSharedFilesCollection()) {
                 c.isSharedFilesCollection()) {
               outgoing.add(
               outgoing.add(
                 CollectionWithThumbnail(
                 CollectionWithThumbnail(
@@ -112,7 +112,7 @@ class _SharedCollectionGalleryState extends State<SharedCollectionGallery>
       }),
       }),
       builder: (context, snapshot) {
       builder: (context, snapshot) {
         if (snapshot.hasData) {
         if (snapshot.hasData) {
-          return _getSharedCollectionsGallery(snapshot.data);
+          return _getSharedCollectionsGallery(snapshot.data!);
         } else if (snapshot.hasError) {
         } else if (snapshot.hasError) {
           _logger.shout(snapshot.error);
           _logger.shout(snapshot.error);
           return Center(child: Text(snapshot.error.toString()));
           return Center(child: Text(snapshot.error.toString()));
@@ -264,20 +264,20 @@ class OutgoingCollectionItem extends StatelessWidget {
 
 
   const OutgoingCollectionItem(
   const OutgoingCollectionItem(
     this.c, {
     this.c, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final sharees = <String>[];
-    for (int index = 0; index < c.collection.sharees.length; index++) {
-      final sharee = c.collection.sharees[index];
+    final sharees = <String?>[];
+    for (int index = 0; index < c.collection.sharees!.length; index++) {
+      final sharee = c.collection.sharees![index]!;
       final name =
       final name =
           (sharee.name?.isNotEmpty ?? false) ? sharee.name : sharee.email;
           (sharee.name?.isNotEmpty ?? false) ? sharee.name : sharee.email;
       if (index < 2) {
       if (index < 2) {
         sharees.add(name);
         sharees.add(name);
       } else {
       } else {
-        final remaining = c.collection.sharees.length - index;
+        final remaining = c.collection.sharees!.length - index;
         if (remaining == 1) {
         if (remaining == 1) {
           // If it's the last sharee
           // If it's the last sharee
           sharees.add(name);
           sharees.add(name);
@@ -304,10 +304,10 @@ class OutgoingCollectionItem extends StatelessWidget {
                 height: 60,
                 height: 60,
                 width: 60,
                 width: 60,
                 child: Hero(
                 child: Hero(
-                  tag: "outgoing_collection" + c.thumbnail.tag,
+                  tag: "outgoing_collection" + c.thumbnail!.tag,
                   child: ThumbnailWidget(
                   child: ThumbnailWidget(
                     c.thumbnail,
                     c.thumbnail,
-                    key: Key("outgoing_collection" + c.thumbnail.tag),
+                    key: Key("outgoing_collection" + c.thumbnail!.tag),
                   ),
                   ),
                 ),
                 ),
               ),
               ),
@@ -320,15 +320,15 @@ class OutgoingCollectionItem extends StatelessWidget {
                   Row(
                   Row(
                     children: [
                     children: [
                       Text(
                       Text(
-                        c.collection.name,
+                        c.collection.name!,
                         style: const TextStyle(
                         style: const TextStyle(
                           fontSize: 16,
                           fontSize: 16,
                         ),
                         ),
                       ),
                       ),
                       const Padding(padding: EdgeInsets.all(2)),
                       const Padding(padding: EdgeInsets.all(2)),
-                      c.collection.publicURLs.isEmpty
+                      c.collection.publicURLs!.isEmpty
                           ? Container()
                           ? Container()
-                          : (c.collection.publicURLs.first.isExpired
+                          : (c.collection.publicURLs!.first!.isExpired
                               ? const Icon(
                               ? const Icon(
                                   Icons.link,
                                   Icons.link,
                                   color: warning500,
                                   color: warning500,
@@ -373,7 +373,7 @@ class IncomingCollectionItem extends StatelessWidget {
 
 
   const IncomingCollectionItem(
   const IncomingCollectionItem(
     this.c, {
     this.c, {
-    Key key,
+    Key? key,
   }) : super(key: key);
   }) : super(key: key);
 
 
   @override
   @override
@@ -381,7 +381,7 @@ class IncomingCollectionItem extends StatelessWidget {
     const double horizontalPaddingOfGridRow = 16;
     const double horizontalPaddingOfGridRow = 16;
     const double crossAxisSpacingOfGrid = 9;
     const double crossAxisSpacingOfGrid = 9;
     final TextStyle albumTitleTextStyle =
     final TextStyle albumTitleTextStyle =
-        Theme.of(context).textTheme.subtitle1.copyWith(fontSize: 14);
+        Theme.of(context).textTheme.subtitle1!.copyWith(fontSize: 14);
     final Size size = MediaQuery.of(context).size;
     final Size size = MediaQuery.of(context).size;
     final int albumsCountInOneRow = max(size.width ~/ 220.0, 2);
     final int albumsCountInOneRow = max(size.width ~/ 220.0, 2);
     final double totalWhiteSpaceOfRow = (horizontalPaddingOfGridRow * 2) +
     final double totalWhiteSpaceOfRow = (horizontalPaddingOfGridRow * 2) +
@@ -400,10 +400,10 @@ class IncomingCollectionItem extends StatelessWidget {
               child: Stack(
               child: Stack(
                 children: [
                 children: [
                   Hero(
                   Hero(
-                    tag: "shared_collection" + c.thumbnail.tag,
+                    tag: "shared_collection" + c.thumbnail!.tag,
                     child: ThumbnailWidget(
                     child: ThumbnailWidget(
                       c.thumbnail,
                       c.thumbnail,
-                      key: Key("shared_collection" + c.thumbnail.tag),
+                      key: Key("shared_collection" + c.thumbnail!.tag),
                     ),
                     ),
                   ),
                   ),
                   Align(
                   Align(
@@ -411,7 +411,7 @@ class IncomingCollectionItem extends StatelessWidget {
                     child: Padding(
                     child: Padding(
                       padding: const EdgeInsets.only(right: 8.0, bottom: 8.0),
                       padding: const EdgeInsets.only(right: 8.0, bottom: 8.0),
                       child: UserAvatarWidget(
                       child: UserAvatarWidget(
-                        c.collection.owner,
+                        c.collection.owner!,
                         thumbnailView: true,
                         thumbnailView: true,
                       ),
                       ),
                     ),
                     ),
@@ -426,7 +426,7 @@ class IncomingCollectionItem extends StatelessWidget {
               Container(
               Container(
                 constraints: BoxConstraints(maxWidth: sideOfThumbnail - 40),
                 constraints: BoxConstraints(maxWidth: sideOfThumbnail - 40),
                 child: Text(
                 child: Text(
-                  c.collection.name,
+                  c.collection.name!,
                   style: albumTitleTextStyle,
                   style: albumTitleTextStyle,
                   overflow: TextOverflow.ellipsis,
                   overflow: TextOverflow.ellipsis,
                 ),
                 ),
@@ -434,11 +434,11 @@ class IncomingCollectionItem extends StatelessWidget {
               FutureBuilder<int>(
               FutureBuilder<int>(
                 future: FilesDB.instance.collectionFileCount(c.collection.id),
                 future: FilesDB.instance.collectionFileCount(c.collection.id),
                 builder: (context, snapshot) {
                 builder: (context, snapshot) {
-                  if (snapshot.hasData && snapshot.data > 0) {
+                  if (snapshot.hasData && snapshot.data! > 0) {
                     return RichText(
                     return RichText(
                       text: TextSpan(
                       text: TextSpan(
                         style: albumTitleTextStyle.copyWith(
                         style: albumTitleTextStyle.copyWith(
-                          color: albumTitleTextStyle.color.withOpacity(0.5),
+                          color: albumTitleTextStyle.color!.withOpacity(0.5),
                         ),
                         ),
                         children: [
                         children: [
                           const TextSpan(text: "  \u2022  "),
                           const TextSpan(text: "  \u2022  "),

+ 1 - 1
lib/ui/sharing/add_partipant_page.dart

@@ -313,7 +313,7 @@ class _AddParticipantPage extends State<AddParticipantPage> {
     }
     }
     for (final c in CollectionsService.instance.getActiveCollections()) {
     for (final c in CollectionsService.instance.getActiveCollections()) {
       if (c.owner?.id == ownerID) {
       if (c.owner?.id == ownerID) {
-        for (final User? u in c?.sharees ?? []) {
+        for (final User? u in c.sharees ?? []) {
           if (u != null && u.id != null && !existingUserIDs.contains(u.id)) {
           if (u != null && u.id != null && !existingUserIDs.contains(u.id)) {
             existingUserIDs.add(u.id!);
             existingUserIDs.add(u.id!);
             suggestedUsers.add(u);
             suggestedUsers.add(u);

Some files were not shown because too many files changed in this diff